"""Tests that generated Python code passes mypy type checking.

Generates all backend code with a representative spec, writes the matching
pyproject.toml mypy config, then verifies mypy reports zero errors.

Marked @pytest.mark.slow because it runs mypy as a subprocess.
"""

from __future__ import annotations

import shutil
import subprocess
from typing import TYPE_CHECKING

import pytest

from prisme.generators.backend import (
    AdminGenerator,
    AlembicGenerator,
    APIKeyAuthGenerator,
    AuthGenerator,
    GraphQLGenerator,
    MCPGenerator,
    ModelsGenerator,
    RESTGenerator,
    SchemasGenerator,
    ServicesGenerator,
)
from prisme.generators.base import GeneratorContext
from prisme.generators.testing.backend import BackendTestGenerator
from prisme.spec.fields import FieldSpec, FieldType
from prisme.spec.model import ModelSpec, RelationshipSpec
from prisme.spec.project import ProjectSpec
from prisme.spec.stack import StackSpec

if TYPE_CHECKING:
    from pathlib import Path


@pytest.mark.slow
class TestGeneratedPythonMypyClean:
    """Verify that all generated backend Python passes mypy --strict."""

    @pytest.fixture(scope="class")
    def stack_spec(self) -> StackSpec:
        """Create a representative stack spec with relationships, auth, etc."""
        return StackSpec(
            name="test-mypy",
            version="1.0.0",
            models=[
                ModelSpec(
                    name="Customer",
                    description="Customer entity",
                    soft_delete=True,
                    timestamps=True,
                    fields=[
                        FieldSpec(
                            name="name", type=FieldType.STRING, required=True, max_length=255
                        ),
                        FieldSpec(
                            name="email",
                            type=FieldType.STRING,
                            required=True,
                            unique=True,
                            max_length=255,
                        ),
                        FieldSpec(
                            name="status",
                            type=FieldType.ENUM,
                            enum_values=["active", "inactive"],
                            default="active",
                        ),
                    ],
                    relationships=[
                        RelationshipSpec(
                            name="orders",
                            target_model="Order",
                            type="one_to_many",
                            back_populates="customer",
                        ),
                    ],
                ),
                ModelSpec(
                    name="Order",
                    description="Order entity",
                    timestamps=True,
                    fields=[
                        FieldSpec(name="order_number", type=FieldType.STRING, required=True),
                        FieldSpec(name="total", type=FieldType.DECIMAL, required=True),
                        FieldSpec(
                            name="customer_id",
                            type=FieldType.FOREIGN_KEY,
                            references="Customer",
                        ),
                    ],
                    relationships=[
                        RelationshipSpec(
                            name="customer",
                            target_model="Customer",
                            type="many_to_one",
                            back_populates="orders",
                        ),
                        RelationshipSpec(
                            name="items",
                            target_model="OrderItem",
                            type="one_to_many",
                            back_populates="order",
                        ),
                    ],
                    nested_create=["items"],
                ),
                ModelSpec(
                    name="OrderItem",
                    description="Order line item",
                    fields=[
                        FieldSpec(name="product_name", type=FieldType.STRING, required=True),
                        FieldSpec(name="quantity", type=FieldType.INTEGER, required=True),
                        FieldSpec(
                            name="order_id",
                            type=FieldType.FOREIGN_KEY,
                            references="Order",
                        ),
                    ],
                    relationships=[
                        RelationshipSpec(
                            name="order",
                            target_model="Order",
                            type="many_to_one",
                            back_populates="items",
                        ),
                    ],
                ),
            ],
        )

    @pytest.fixture(scope="class")
    def generated_project(
        self, stack_spec: StackSpec, tmp_path_factory: pytest.TempPathFactory
    ) -> Path:
        """Generate all backend code and write pyproject.toml with mypy config."""
        root = tmp_path_factory.mktemp("mypy-check")
        pkg_name = "test_mypy"

        context = GeneratorContext(
            domain_spec=stack_spec,
            output_dir=root,
            project_spec=ProjectSpec(name="test-mypy"),
        )

        # Run all backend generators including tests
        generators = [
            ModelsGenerator,
            AlembicGenerator,
            SchemasGenerator,
            ServicesGenerator,
            AuthGenerator,
            AdminGenerator,
            APIKeyAuthGenerator,
            RESTGenerator,
            GraphQLGenerator,
            MCPGenerator,
            BackendTestGenerator,
        ]

        for gen_cls in generators:
            try:
                gen = gen_cls(context)
                result = gen.generate()
                for f in result.files:
                    file_path = root / f.path
                    file_path.parent.mkdir(parents=True, exist_ok=True)
                    file_path.write_text(f.content)
            except Exception:
                pass

        # Write pyproject.toml with mypy config matching the template
        pyproject = root / "pyproject.toml"
        pyproject.write_text(f"""\
[tool.mypy]
python_version = "3.13"
strict = true
ignore_missing_imports = true
plugins = ["pydantic.mypy"]
disallow_any_generics = false
mypy_path = ["packages/backend/src", "packages/backend"]
explicit_package_bases = true

[[tool.mypy.overrides]]
module = ["tests.*", "{pkg_name}.tests.*"]
ignore_errors = true

[[tool.mypy.overrides]]
module = [
    "{pkg_name}.api.graphql.*",
    "{pkg_name}.api.rest._generated.*",
    "{pkg_name}.mcp_server.*",
    "{pkg_name}.extensions.*",
    "{pkg_name}.services._generated.*",
    "{pkg_name}.schemas.*",
    "{pkg_name}.models.*",
    "{pkg_name}.admin.*",
    "{pkg_name}.middleware.*",
    "{pkg_name}.auth.*",
    "{pkg_name}.alembic.*",
    "alembic.*",
]
ignore_errors = true
""")

        # Run ruff format first to ensure valid syntax
        if shutil.which("ruff"):
            subprocess.run(
                ["ruff", "check", "--fix", str(root)],
                capture_output=True,
                timeout=120,
            )
            subprocess.run(
                ["ruff", "format", str(root)],
                capture_output=True,
                timeout=120,
            )

        return root

    def test_mypy_passes(self, generated_project: Path) -> None:
        """All generated Python files pass mypy --strict."""
        if not shutil.which("mypy"):
            pytest.skip("mypy not installed")

        result = subprocess.run(
            ["mypy", str(generated_project)],
            capture_output=True,
            text=True,
            timeout=120,
            cwd=generated_project,
        )
        assert result.returncode == 0, f"mypy failed:\n{result.stdout}\n{result.stderr}"
