"""Test that generated backend tests actually pass when executed.

Generates all backend code + test code with a representative spec,
installs runtime dependencies into a temp venv, then runs pytest
on the generated unit tests.

Marked @pytest.mark.slow because it installs packages and runs pytest.
"""

from __future__ import annotations

import shutil
import subprocess
import textwrap
from typing import TYPE_CHECKING

import pytest

from prisme.generators.backend import (
    ModelsGenerator,
    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 GeneratorConfig, ProjectSpec, TestingConfig
from prisme.spec.stack import StackSpec

if TYPE_CHECKING:
    from pathlib import Path


@pytest.mark.slow
class TestGeneratedTestsPass:
    """Verify that generated backend unit tests pass when executed."""

    @pytest.fixture(scope="class")
    def stack_spec(self) -> StackSpec:
        """Representative spec with relationships for realistic generated tests."""
        return StackSpec(
            name="test-app",
            version="1.0.0",
            models=[
                ModelSpec(
                    name="Customer",
                    description="Customer entity",
                    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,
                        ),
                    ],
                    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",
                        ),
                    ],
                ),
            ],
        )

    @pytest.fixture(scope="class")
    def generated_project(
        self, stack_spec: StackSpec, tmp_path_factory: pytest.TempPathFactory
    ) -> Path:
        """Generate all backend code + tests into a temp directory."""
        root = tmp_path_factory.mktemp("gen-tests")

        project_spec = ProjectSpec(
            name="test-app",
            generator=GeneratorConfig(
                backend_output="packages/backend/src",
                tests_path="tests",
            ),
            testing=TestingConfig(
                generate_factories=True,
                generate_unit_tests=True,
                generate_integration_tests=False,
                generate_graphql_tests=False,
                test_database="sqlite",
            ),
        )

        context = GeneratorContext(
            domain_spec=stack_spec,
            output_dir=root,
            project_spec=project_spec,
        )

        # Generate backend code (models, schemas, services)
        for gen_cls in [ModelsGenerator, SchemasGenerator, ServicesGenerator]:
            gen = gen_cls(context)
            gen.generate()

        # Generate tests
        test_gen = BackendTestGenerator(context)
        test_gen.generate()

        # Run ruff fix + format on generated Python
        py_files = [str(p) for p in root.rglob("*.py")]
        if py_files and shutil.which("ruff"):
            subprocess.run(
                ["ruff", "check", "--fix", *py_files],
                capture_output=True,
                timeout=120,
            )
            subprocess.run(
                ["ruff", "format", *py_files],
                capture_output=True,
                timeout=120,
            )

        # Ensure tests directories are Python packages
        tests_dir = root / "packages" / "backend" / "tests"
        for d in [tests_dir, tests_dir / "unit", tests_dir / "factories"]:
            d.mkdir(parents=True, exist_ok=True)
            init = d / "__init__.py"
            if not init.exists():
                init.write_text("")

        # Write a minimal pyproject.toml for the generated project
        backend_root = root / "packages" / "backend"
        (backend_root / "pyproject.toml").write_text(
            textwrap.dedent("""\
                [project]
                name = "test-app"
                version = "0.1.0"
                requires-python = ">=3.13"
                dependencies = [
                    "sqlalchemy[asyncio]>=2.0",
                    "aiosqlite>=0.20",
                    "pydantic>=2.10",
                    "factory-boy>=3.3",
                    "pytest>=8.0",
                    "pytest-asyncio>=0.24",
                ]

                [tool.pytest.ini_options]
                asyncio_mode = "auto"
                pythonpath = ["src", "."]
            """)
        )

        return backend_root

    def test_generated_unit_tests_pass(self, generated_project: Path) -> None:
        """Run pytest on the generated unit tests and verify they pass."""
        if not shutil.which("uv"):
            pytest.skip("uv not installed")

        # Install deps and run unit tests
        result = subprocess.run(
            [
                "uv",
                "run",
                "--project",
                str(generated_project),
                "pytest",
                str(generated_project / "tests" / "unit"),
                "-v",
                "--tb=short",
                "--no-header",
                "-x",
            ],
            capture_output=True,
            text=True,
            timeout=300,
            cwd=str(generated_project),
        )

        assert result.returncode == 0, (
            f"Generated unit tests failed:\n{result.stdout}\n{result.stderr}"
        )
