"""Advanced tests for the generate command targeting uncovered code paths."""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

from prisme.cli import main

if TYPE_CHECKING:
    from click.testing import CliRunner


VALID_SPEC = """\
from prisme.spec.stack import StackSpec
from prisme.spec.model import ModelSpec
from prisme.spec.fields import FieldSpec, FieldType

spec = StackSpec(
    name="test-project",
    version="1.0.0",
    models=[
        ModelSpec(
            name="Item",
            fields=[FieldSpec(name="name", type=FieldType.STRING, required=True)],
        )
    ],
)
"""

SPEC_WITH_AUTH = """\
from prisme.spec.stack import StackSpec
from prisme.spec.model import ModelSpec
from prisme.spec.fields import FieldSpec, FieldType

spec = StackSpec(
    name="test-project",
    version="1.0.0",
    models=[
        ModelSpec(
            name="User",
            fields=[
                FieldSpec(name="name", type=FieldType.STRING, required=True),
                FieldSpec(name="email", type=FieldType.STRING, required=True, unique=True),
            ],
        )
    ],
)
"""

SPEC_WITH_RELATIONSHIPS = """\
from prisme.spec.stack import StackSpec
from prisme.spec.model import ModelSpec
from prisme.spec.fields import FieldSpec, FieldType
from prisme.spec.model import RelationshipSpec

spec = StackSpec(
    name="test-project",
    version="1.0.0",
    models=[
        ModelSpec(
            name="Author",
            fields=[
                FieldSpec(name="name", type=FieldType.STRING, required=True),
            ],
            relationships=[
                RelationshipSpec(name="books", target_model="Book", type="one_to_many"),
            ],
        ),
        ModelSpec(
            name="Book",
            fields=[
                FieldSpec(name="title", type=FieldType.STRING, required=True),
                FieldSpec(name="author_id", type=FieldType.INTEGER, required=True, references="Author"),
            ],
        ),
    ],
)
"""


class TestGenerateWithProjectSpec:
    """Tests for generate command with project spec configurations."""

    def test_generate_with_auth_project_spec(self, cli_runner: CliRunner) -> None:
        """Generate with auth config includes auth generators."""
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(SPEC_WITH_AUTH)

            # Create project spec with auth enabled
            (specs_dir / "project.py").write_text(
                """\
from prisme.spec.project import ProjectSpec
from prisme.spec.auth import AuthConfig

project = ProjectSpec(
    name="test-project",
    auth=AuthConfig(enabled=True),
)
"""
            )

            # Create prisme.toml pointing to project spec
            Path("prisme.toml").write_text(
                'prisme_version = "0.12.1"\nconfig_version = 1\n\n'
                "[project]\n"
                'spec_path = "specs/models.py"\n'
                'project_path = "specs/project.py"\n'
            )

            result = cli_runner.invoke(main, ["generate", "specs/models.py"])
            assert result.exit_code == 0

    def test_generate_with_graphql_project_spec(self, cli_runner: CliRunner) -> None:
        """Generate with GraphQL exposure config."""
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(SPEC_WITH_AUTH)

            (specs_dir / "project.py").write_text(
                """\
from prisme.spec.project import ProjectSpec, ExposureConfig
from prisme.spec.exposure import GraphQLExposureConfig

project = ProjectSpec(
    name="test-project",
    exposure=ExposureConfig(
        graphql=GraphQLExposureConfig(enabled=True),
    ),
)
"""
            )

            Path("prisme.toml").write_text(
                'prisme_version = "0.12.1"\nconfig_version = 1\n\n'
                "[project]\n"
                'spec_path = "specs/models.py"\n'
                'project_path = "specs/project.py"\n'
            )

            result = cli_runner.invoke(main, ["generate", "specs/models.py"])
            assert result.exit_code == 0

    def test_generate_with_testing_project_spec(self, cli_runner: CliRunner) -> None:
        """Generate with testing config includes test generators."""
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(SPEC_WITH_AUTH)

            (specs_dir / "project.py").write_text(
                """\
from prisme.spec.project import ProjectSpec, TestingConfig

project = ProjectSpec(
    name="test-project",
    testing=TestingConfig(enabled=True),
)
"""
            )

            Path("prisme.toml").write_text(
                'prisme_version = "0.12.1"\nconfig_version = 1\n\n'
                "[project]\n"
                'spec_path = "specs/models.py"\n'
                'project_path = "specs/project.py"\n'
            )

            result = cli_runner.invoke(main, ["generate", "specs/models.py"])
            assert result.exit_code == 0


class TestGenerateWithRelationships:
    """Tests for generate with relational models."""

    def test_generate_relational_spec(self, cli_runner: CliRunner) -> None:
        """Generate with relational models creates all related files."""
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(SPEC_WITH_RELATIONSHIPS)

            result = cli_runner.invoke(main, ["generate", "specs/models.py"])
            assert result.exit_code == 0
            assert "Summary" in result.output or "Total" in result.output


class TestGenerateDiff:
    """Tests for generate --diff flag."""

    def test_generate_diff_flag(self, cli_runner: CliRunner) -> None:
        """Generate --diff shows changes."""
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(VALID_SPEC)

            result = cli_runner.invoke(main, ["generate", "specs/models.py", "--diff"])
            # --diff should work (may not show diff on first run)
            assert result.exit_code == 0


class TestGenerateOnlyLayers:
    """Tests for generate --only option with various layers."""

    def test_generate_only_models(self, cli_runner: CliRunner) -> None:
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(VALID_SPEC)

            result = cli_runner.invoke(main, ["generate", "specs/models.py", "--only", "models"])
            assert result.exit_code == 0

    def test_generate_only_schemas(self, cli_runner: CliRunner) -> None:
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(VALID_SPEC)

            result = cli_runner.invoke(main, ["generate", "specs/models.py", "--only", "schemas"])
            assert result.exit_code == 0

    def test_generate_only_services(self, cli_runner: CliRunner) -> None:
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(VALID_SPEC)

            result = cli_runner.invoke(main, ["generate", "specs/models.py", "--only", "services"])
            assert result.exit_code == 0

    def test_generate_only_rest(self, cli_runner: CliRunner) -> None:
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(VALID_SPEC)

            result = cli_runner.invoke(main, ["generate", "specs/models.py", "--only", "rest"])
            assert result.exit_code == 0

    def test_generate_only_frontend(self, cli_runner: CliRunner) -> None:
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(VALID_SPEC)

            result = cli_runner.invoke(main, ["generate", "specs/models.py", "--only", "frontend"])
            assert result.exit_code == 0

    def test_generate_only_backend(self, cli_runner: CliRunner) -> None:
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(VALID_SPEC)

            result = cli_runner.invoke(main, ["generate", "specs/models.py", "--only", "backend"])
            assert result.exit_code == 0


class TestGenerateFromLegacyConfig:
    """Tests for generate reading from prism.config.py (legacy)."""

    def test_generate_resolves_from_spec_py(self, cli_runner: CliRunner) -> None:
        """Generate picks up spec.py when it exists (needs --force without prisme.toml)."""
        with cli_runner.isolated_filesystem():
            Path("spec.py").write_text(VALID_SPEC)
            result = cli_runner.invoke(main, ["generate", "--force"])
            assert result.exit_code == 0


class TestGenerateOutputStructure:
    """Tests for generate command output files."""

    def test_generate_creates_backend_structure(self, cli_runner: CliRunner) -> None:
        """Generate creates the packages/backend directory structure."""
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(VALID_SPEC)

            result = cli_runner.invoke(main, ["generate", "specs/models.py"])
            assert result.exit_code == 0

            backend = Path("packages/backend")
            assert backend.exists()

    def test_generate_creates_frontend_structure(self, cli_runner: CliRunner) -> None:
        """Generate creates the packages/frontend directory structure."""
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(VALID_SPEC)

            result = cli_runner.invoke(main, ["generate", "specs/models.py"])
            assert result.exit_code == 0

            frontend = Path("packages/frontend")
            assert frontend.exists()

    def test_generate_creates_manifest(self, cli_runner: CliRunner) -> None:
        """Generate creates .prisme/manifest.json."""
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(VALID_SPEC)

            result = cli_runner.invoke(main, ["generate", "specs/models.py"])
            assert result.exit_code == 0

            manifest = Path(".prisme/manifest.json")
            assert manifest.exists()

    def test_generate_twice_is_idempotent(self, cli_runner: CliRunner) -> None:
        """Running generate twice should be safe."""
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(VALID_SPEC)

            result1 = cli_runner.invoke(main, ["generate", "specs/models.py"])
            assert result1.exit_code == 0

            result2 = cli_runner.invoke(main, ["generate", "specs/models.py"])
            assert result2.exit_code == 0


class TestDoctorAdvanced:
    """Advanced tests for the doctor command."""

    def test_doctor_with_prisme_toml(self, cli_runner: CliRunner) -> None:
        """Doctor shows positive checks when prisme.toml exists."""
        with cli_runner.isolated_filesystem():
            Path("prisme.toml").write_text(
                'prisme_version = "0.12.1"\nconfig_version = 1\n\n[project]\n'
            )
            result = cli_runner.invoke(main, ["doctor"])
            assert result.exit_code in (0, 1)
            assert "doctor" in result.output.lower()

    def test_doctor_with_spec_file(self, cli_runner: CliRunner) -> None:
        """Doctor shows positive check when spec file exists."""
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(VALID_SPEC)
            result = cli_runner.invoke(main, ["doctor"])
            assert result.exit_code in (0, 1)

    def test_doctor_with_full_project(self, cli_runner: CliRunner) -> None:
        """Doctor with a more complete project."""
        with cli_runner.isolated_filesystem():
            Path("prisme.toml").write_text(
                'prisme_version = "0.12.1"\nconfig_version = 1\n\n[project]\n'
            )
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(VALID_SPEC)
            Path("packages").mkdir()
            Path("packages/backend").mkdir(parents=True)
            Path("packages/frontend").mkdir(parents=True)
            result = cli_runner.invoke(main, ["doctor"])
            assert result.exit_code in (0, 1)
