"""Tests that frontend generators produce valid TypeScript.

Each test runs a generator, then invokes `npx tsc --noEmit` to verify
the output is free of type errors.

Marked @pytest.mark.slow because they require npm install + tsc.
"""

from __future__ import annotations

import json
import shutil
import subprocess
from typing import TYPE_CHECKING

import pytest

from prisme.generators.base import GeneratorContext
from prisme.generators.frontend import (
    ComponentsGenerator,
    DashboardGenerator,
    DesignSystemGenerator,
    ErrorPagesGenerator,
    FrontendAuthGenerator,
    GraphQLOpsGenerator,
    HeadlessGenerator,
    HooksGenerator,
    LandingPageGenerator,
    PagesGenerator,
    ProfilePagesGenerator,
    RouterGenerator,
    SearchPageGenerator,
    TypeScriptGenerator,
    WidgetSystemGenerator,
)
from prisme.spec.fields import FieldSpec, FieldType
from prisme.spec.model import ModelSpec
from prisme.spec.project import GeneratorConfig, ProjectSpec
from prisme.spec.stack import StackSpec

if TYPE_CHECKING:
    from pathlib import Path


def _make_package_json() -> dict:
    """Minimal package.json for type-checking generated code."""
    return {
        "name": "prism-typecheck-test",
        "private": True,
        "dependencies": {
            "react": "^18.2.0",
            "react-dom": "^18.2.0",
            "react-router-dom": "^6.20.0",
            "@apollo/client": "^3.8.0",
            "graphql": "^16.8.0",
            "urql": "^4.0.0",
            "graphql-ws": "^5.14.0",
            "react-hook-form": "^7.49.0",
            "lucide-react": "^0.300.0",
        },
        "devDependencies": {
            "typescript": "^5.3.0",
            "@types/react": "^18.2.0",
            "@types/react-dom": "^18.2.0",
            "vite": "^5.0.0",
        },
    }


def _make_tsconfig() -> dict:
    """Minimal tsconfig.json for type-checking generated code."""
    return {
        "compilerOptions": {
            "target": "ES2020",
            "module": "ESNext",
            "moduleResolution": "bundler",
            "jsx": "react-jsx",
            "strict": True,
            "esModuleInterop": True,
            "skipLibCheck": True,
            "noEmit": True,
            "baseUrl": ".",
            "paths": {"@/*": ["src/*"]},
        },
        "include": ["src"],
    }


VITE_ENV_DTS = '/// <reference types="vite/client" />\n'


@pytest.mark.slow
class TestFrontendTypechecks:
    """Verify each frontend generator produces valid TypeScript."""

    @pytest.fixture(scope="class")
    def frontend_project(self, tmp_path_factory: pytest.TempPathFactory) -> Path:
        """Set up a minimal React+TS project with dependencies installed."""
        root = tmp_path_factory.mktemp("frontend")
        (root / "src").mkdir()

        (root / "package.json").write_text(json.dumps(_make_package_json(), indent=2))
        (root / "tsconfig.json").write_text(json.dumps(_make_tsconfig(), indent=2))

        # Write Vite env declaration for import.meta.env support
        (root / "src" / "vite-env.d.ts").write_text(VITE_ENV_DTS)

        result = subprocess.run(
            ["npm", "install", "--ignore-scripts"],
            cwd=root,
            capture_output=True,
            timeout=120,
        )
        if result.returncode != 0:
            pytest.skip(f"npm install failed: {result.stderr.decode()}")

        return root

    @pytest.fixture(autouse=True)
    def _clean_src(self, frontend_project: Path) -> None:
        """Clean generated files between tests to prevent accumulation."""
        src = frontend_project / "src"
        for child in src.iterdir():
            # Keep vite-env.d.ts and node_modules
            if child.name == "vite-env.d.ts":
                continue
            if child.is_dir():
                shutil.rmtree(child)
            else:
                child.unlink()

    @pytest.fixture
    def stack_spec(self) -> StackSpec:
        """Create a representative stack spec."""
        return StackSpec(
            name="test-project",
            version="1.0.0",
            description="Test project",
            models=[
                ModelSpec(
                    name="Customer",
                    description="Customer entity",
                    timestamps=True,
                    fields=[
                        FieldSpec(name="name", type=FieldType.STRING, required=True),
                        FieldSpec(name="email", type=FieldType.STRING, required=True),
                        FieldSpec(name="age", type=FieldType.INTEGER, required=False),
                        FieldSpec(name="is_active", type=FieldType.BOOLEAN, required=False),
                    ],
                ),
            ],
        )

    @pytest.fixture
    def generator_context(self, frontend_project: Path, stack_spec: StackSpec) -> GeneratorContext:
        """Create a GeneratorContext pointing at the frontend project."""
        # The generators write to output_dir / <frontend_output> / ...
        # We need output_dir such that frontend_output resolves to frontend_project/src
        # Default frontend_output is "packages/frontend/src"
        # So set output_dir to a parent that makes it work, OR override the spec.
        # Easiest: override generator config so frontend_output points at our tmp dir.
        project = ProjectSpec(
            name="test-project",
            generator=GeneratorConfig(frontend_output="src"),
        )
        return GeneratorContext(
            domain_spec=stack_spec,
            output_dir=frontend_project,
            project_spec=project,
        )

    def _run_tsc(self, frontend_project: Path) -> subprocess.CompletedProcess:
        """Run tsc --noEmit and return the result."""
        return subprocess.run(
            ["npx", "tsc", "--noEmit"],
            cwd=frontend_project,
            capture_output=True,
            text=True,
            timeout=60,
        )

    def _run_generators(self, generator_context: GeneratorContext, *gen_classes: type) -> None:
        """Run multiple generators, ignoring errors from optional ones."""
        for cls in gen_classes:
            try:
                cls(generator_context).generate()
            except Exception:
                pass

    def test_typescript_generator(
        self, generator_context: GeneratorContext, frontend_project: Path
    ) -> None:
        gen = TypeScriptGenerator(generator_context)
        gen.generate()
        result = self._run_tsc(frontend_project)
        assert result.returncode == 0, f"tsc failed:\n{result.stdout}\n{result.stderr}"

    def test_graphql_ops_generator(
        self, generator_context: GeneratorContext, frontend_project: Path
    ) -> None:
        # GraphQL ops depends on types
        self._run_generators(generator_context, TypeScriptGenerator, GraphQLOpsGenerator)
        result = self._run_tsc(frontend_project)
        assert result.returncode == 0, f"tsc failed:\n{result.stdout}\n{result.stderr}"

    def test_headless_generator(
        self, generator_context: GeneratorContext, frontend_project: Path
    ) -> None:
        self._run_generators(generator_context, TypeScriptGenerator, HeadlessGenerator)
        result = self._run_tsc(frontend_project)
        assert result.returncode == 0, f"tsc failed:\n{result.stdout}\n{result.stderr}"

    def test_design_system_generator(
        self, generator_context: GeneratorContext, frontend_project: Path
    ) -> None:
        gen = DesignSystemGenerator(generator_context)
        gen.generate()
        result = self._run_tsc(frontend_project)
        assert result.returncode == 0, f"tsc failed:\n{result.stdout}\n{result.stderr}"

    def test_widget_system_generator(
        self, generator_context: GeneratorContext, frontend_project: Path
    ) -> None:
        self._run_generators(
            generator_context, TypeScriptGenerator, GraphQLOpsGenerator, WidgetSystemGenerator
        )
        result = self._run_tsc(frontend_project)
        assert result.returncode == 0, f"tsc failed:\n{result.stdout}\n{result.stderr}"

    def test_components_generator(
        self, generator_context: GeneratorContext, frontend_project: Path
    ) -> None:
        self._run_generators(
            generator_context,
            TypeScriptGenerator,
            GraphQLOpsGenerator,
            HeadlessGenerator,
            WidgetSystemGenerator,
            ComponentsGenerator,
        )
        result = self._run_tsc(frontend_project)
        assert result.returncode == 0, f"tsc failed:\n{result.stdout}\n{result.stderr}"

    def test_hooks_generator(
        self, generator_context: GeneratorContext, frontend_project: Path
    ) -> None:
        self._run_generators(
            generator_context,
            TypeScriptGenerator,
            GraphQLOpsGenerator,
            HeadlessGenerator,
            HooksGenerator,
        )
        result = self._run_tsc(frontend_project)
        assert result.returncode == 0, f"tsc failed:\n{result.stdout}\n{result.stderr}"

    def test_pages_generator(
        self, generator_context: GeneratorContext, frontend_project: Path
    ) -> None:
        self._run_generators(
            generator_context,
            TypeScriptGenerator,
            GraphQLOpsGenerator,
            HeadlessGenerator,
            WidgetSystemGenerator,
            ComponentsGenerator,
            HooksGenerator,
            PagesGenerator,
        )
        result = self._run_tsc(frontend_project)
        assert result.returncode == 0, f"tsc failed:\n{result.stdout}\n{result.stderr}"

    def test_router_generator(
        self, generator_context: GeneratorContext, frontend_project: Path
    ) -> None:
        self._run_generators(
            generator_context,
            TypeScriptGenerator,
            GraphQLOpsGenerator,
            HeadlessGenerator,
            DesignSystemGenerator,
            WidgetSystemGenerator,
            ComponentsGenerator,
            HooksGenerator,
            PagesGenerator,
            ErrorPagesGenerator,
            DashboardGenerator,
            SearchPageGenerator,
            LandingPageGenerator,
            ProfilePagesGenerator,
            RouterGenerator,
        )
        result = self._run_tsc(frontend_project)
        assert result.returncode == 0, f"tsc failed:\n{result.stdout}\n{result.stderr}"

    def test_frontend_auth_generator(
        self, generator_context: GeneratorContext, frontend_project: Path
    ) -> None:
        self._run_generators(
            generator_context,
            TypeScriptGenerator,
            GraphQLOpsGenerator,
            HeadlessGenerator,
            DesignSystemGenerator,
            WidgetSystemGenerator,
            ComponentsGenerator,
            HooksGenerator,
            PagesGenerator,
            ErrorPagesGenerator,
            DashboardGenerator,
            SearchPageGenerator,
            LandingPageGenerator,
            ProfilePagesGenerator,
            RouterGenerator,
            FrontendAuthGenerator,
        )
        result = self._run_tsc(frontend_project)
        assert result.returncode == 0, f"tsc failed:\n{result.stdout}\n{result.stderr}"
