"""Tests for migrate, plan, and apply CLI commands."""

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)],
        )
    ],
)
"""


class TestMigrateCommand:
    """Tests for the migrate command."""

    def test_migrate_up_to_date(self, cli_runner: CliRunner) -> None:
        """Migrate says up to date when no migration is needed."""
        with cli_runner.isolated_filesystem():
            result = cli_runner.invoke(main, ["migrate"])
            assert result.exit_code == 0
            assert "up to date" in result.output.lower() or "no migrations" in result.output.lower()

    def test_migrate_dry_run(self, cli_runner: CliRunner) -> None:
        """Migrate --dry-run shows what would change."""
        with cli_runner.isolated_filesystem():
            result = cli_runner.invoke(main, ["migrate", "--dry-run"])
            assert result.exit_code == 0

    def test_migrate_with_legacy_config(self, cli_runner: CliRunner) -> None:
        """Migrate detects legacy prism.config.py."""
        with cli_runner.isolated_filesystem():
            # Create a legacy config file
            Path("prism.config.py").write_text(
                "from prisme import PrismConfig\nconfig = PrismConfig(spec_path='spec.py')\n"
            )
            result = cli_runner.invoke(main, ["migrate"])
            # Should detect legacy config even if it can't fully migrate
            assert result.exit_code == 0

    def test_migrate_write_flag(self, cli_runner: CliRunner) -> None:
        """Migrate --write flag is accepted."""
        with cli_runner.isolated_filesystem():
            result = cli_runner.invoke(main, ["migrate", "--write"])
            assert result.exit_code == 0


class TestPlanCLI:
    """Tests for the plan CLI command."""

    def test_plan_with_valid_spec(self, cli_runner: CliRunner) -> None:
        """Plan with valid spec creates plan file."""
        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, ["plan", "--spec", "specs/models.py"])
            assert result.exit_code == 0
            assert "Plan" in result.output
            # Plan file should be created
            assert Path(".prisme/plan.json").exists()

    def test_plan_with_nonexistent_spec(self, cli_runner: CliRunner) -> None:
        """Plan with missing spec file fails."""
        with cli_runner.isolated_filesystem():
            result = cli_runner.invoke(main, ["plan", "--spec", "nonexistent.py"])
            assert result.exit_code != 0
            assert "not found" in result.output.lower()

    def test_plan_default_spec_resolution(self, cli_runner: CliRunner) -> None:
        """Plan resolves spec from default path."""
        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, ["plan"])
            assert result.exit_code == 0

    def test_plan_shows_summary(self, cli_runner: CliRunner) -> None:
        """Plan displays file summary."""
        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, ["plan", "--spec", "specs/models.py"])
            assert result.exit_code == 0
            assert (
                "create" in result.output.lower()
                or "modify" in result.output.lower()
                or "skip" in result.output.lower()
            )

    def test_plan_from_prisme_toml(self, cli_runner: CliRunner) -> None:
        """Plan resolves spec from prisme.toml."""
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(VALID_SPEC)
            Path("prisme.toml").write_text(
                'prisme_version = "0.12.1"\nconfig_version = 1\n\n[project]\nspec_path = "specs/models.py"\n'
            )

            result = cli_runner.invoke(main, ["plan"])
            assert result.exit_code == 0


class TestApplyCLI:
    """Tests for the apply CLI command."""

    def test_apply_without_plan(self, cli_runner: CliRunner) -> None:
        """Apply without saved plan fails."""
        with cli_runner.isolated_filesystem():
            result = cli_runner.invoke(main, ["apply"])
            assert result.exit_code != 0

    def test_apply_after_plan(self, cli_runner: CliRunner) -> None:
        """Apply after successful plan works."""
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(VALID_SPEC)

            # First create a plan
            cli_runner.invoke(main, ["plan", "--spec", "specs/models.py"])
            assert Path(".prisme/plan.json").exists()

            # Then apply it
            result = cli_runner.invoke(main, ["apply"])
            assert result.exit_code == 0
            assert "applied" in result.output.lower() or "Applied" in result.output

    def test_apply_removes_plan_file(self, cli_runner: CliRunner) -> None:
        """Apply removes the plan file after execution."""
        with cli_runner.isolated_filesystem():
            specs_dir = Path("specs")
            specs_dir.mkdir()
            (specs_dir / "models.py").write_text(VALID_SPEC)

            # Create a plan
            cli_runner.invoke(main, ["plan", "--spec", "specs/models.py"])
            assert Path(".prisme/plan.json").exists()

            # Apply it
            result = cli_runner.invoke(main, ["apply"])
            assert result.exit_code == 0
            # Plan file should be removed
            assert not Path(".prisme/plan.json").exists()
