import json
import os
import shutil
import sys
from pathlib import Path
from unittest.mock import patch

import pytest

DIR_PROJECT_ROOT = Path(__file__).parent.parent
DIR_TESTDATA = Path(__file__).parent / "testdata"

DIR_DOCS_GENERATED = DIR_PROJECT_ROOT / "docs" / "_generated"
DIR_TEST_GENERATED = DIR_TESTDATA / "docs" / "_generated"

GITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS")


@pytest.mark.skipif(GITHUB_ACTIONS == "true", reason="Skipped on GitHub Actions - TODO!")
def test_openapi_spec_current(config_eos, set_other_timezone):
    """Verify the openapi spec hasn´t changed."""
    set_other_timezone("UTC") # CI runs on UTC

    expected_spec_path = DIR_PROJECT_ROOT / "openapi.json"
    new_spec_path = DIR_TESTDATA / "openapi-new.json"

    with expected_spec_path.open("r", encoding="utf-8", newline=None) as f_expected:
        expected_spec = json.load(f_expected)

    # Patch get_config and import within guard to patch global variables within the eos module.
    with patch("akkudoktoreos.core.coreabc.get_config", return_value=config_eos):
        # Ensure the script works correctly as part of a package
        root_dir = Path(__file__).resolve().parent.parent
        sys.path.insert(0, str(root_dir))
        from scripts import generate_openapi

        spec = generate_openapi.generate_openapi()
        spec_str = json.dumps(spec, indent=4, sort_keys=True)

    with new_spec_path.open("w", encoding="utf-8", newline="\n") as f_new:
        f_new.write(spec_str)

    # Serialize to ensure comparison is consistent
    expected_spec_str = json.dumps(expected_spec, indent=4, sort_keys=True)

    try:
        assert json.loads(spec_str) == json.loads(expected_spec_str)
    except AssertionError as e:
        pytest.fail(
            f"Expected {new_spec_path} to equal {expected_spec_path}.\n"
            + f"If ok: `make gen-docs` or `cp {new_spec_path} {expected_spec_path}`\n"
        )


@pytest.mark.skipif(GITHUB_ACTIONS == "true", reason="Skipped on GitHub Actions - TODO!")
def test_openapi_md_current(config_eos, set_other_timezone):
    """Verify the generated openapi markdown hasn´t changed."""
    set_other_timezone("UTC") # CI runs on UTC

    expected_spec_md_path = DIR_PROJECT_ROOT / "docs" / "_generated" / "openapi.md"
    new_spec_md_path = DIR_TESTDATA / "openapi-new.md"

    with expected_spec_md_path.open("r", encoding="utf-8", newline=None) as f_expected:
        expected_spec_md = f_expected.read()

    # Patch get_config and import within guard to patch global variables within the eos module.
    with patch("akkudoktoreos.core.coreabc.get_config", return_value=config_eos):
        # Ensure the script works correctly as part of a package
        root_dir = Path(__file__).resolve().parent.parent
        sys.path.insert(0, str(root_dir))
        from scripts import generate_openapi_md

        spec_md = generate_openapi_md.generate_openapi_md()

    with new_spec_md_path.open("w", encoding="utf-8", newline="\n") as f_new:
        f_new.write(spec_md)

    try:
        assert spec_md == expected_spec_md
    except AssertionError as e:
        pytest.fail(
            f"Expected {new_spec_md_path} to equal {expected_spec_md_path}.\n"
            + f"If ok: `make gen-docs` or `cp {new_spec_md_path} {expected_spec_md_path}`\n"
        )


@pytest.mark.skipif(GITHUB_ACTIONS == "true", reason="Skipped on GitHub Actions - TODO!")
def test_config_md_current(config_eos, set_other_timezone):
    """Verify the generated configuration markdown hasn´t changed."""
    set_other_timezone("UTC") # CI runs on UTC

    assert DIR_DOCS_GENERATED.exists()

    # Remove any leftover files from last run
    if DIR_TEST_GENERATED.exists():
        shutil.rmtree(DIR_TEST_GENERATED)

    # Ensure test dir exists
    DIR_TEST_GENERATED.mkdir(parents=True, exist_ok=True)

    # Patch get_config and import within guard to patch global variables within the eos module.
    with patch("akkudoktoreos.core.coreabc.get_config", return_value=config_eos):
        # Ensure the script works correctly as part of a package
        root_dir = Path(__file__).resolve().parent.parent
        sys.path.insert(0, str(root_dir))
        from scripts import generate_config_md

        # Get all the top level fields
        field_names = sorted(config_eos.__class__.model_fields.keys())

        # Create the file paths
        expected = [ DIR_DOCS_GENERATED / "config.md", DIR_DOCS_GENERATED / "configexample.md", ]
        tested = [ DIR_TEST_GENERATED / "config.md", DIR_TEST_GENERATED / "configexample.md", ]
        for field_name in field_names:
            file_name = f"config{field_name.lower()}.md"
            expected.append(DIR_DOCS_GENERATED / file_name)
            tested.append(DIR_TEST_GENERATED / file_name)

        # Create test files
        try:
            config_eos._force_documentation_mode = True
            config_md = generate_config_md.generate_config_md(tested[0], config_eos)
        finally:
            config_eos._force_documentation_mode = False

    # Check test files are the same as the expected files
    for i, expected_path in enumerate(expected):
        tested_path = tested[i]

        with expected_path.open("r", encoding="utf-8", newline=None) as f_expected:
            expected_config_md = f_expected.read()
        with tested_path.open("r", encoding="utf-8", newline=None) as f_expected:
            tested_config_md = f_expected.read()

        try:
            assert tested_config_md == expected_config_md
        except AssertionError as e:
            pytest.fail(
                f"Expected {tested_path} to equal {expected_path}.\n"
                + f"If ok: `make gen-docs` or `cp {tested_path} {expected_path}`\n"
            )
