"""Tests for CLI utility functions and commands."""

from __future__ import annotations

from pathlib import Path

import pytest
from click.testing import CliRunner

from prisme.cli import _resolve_spec_file, main


@pytest.fixture
def cli_runner() -> CliRunner:
    return CliRunner(mix_stderr=False)


class TestResolveSpecFile:
    """Tests for _resolve_spec_file."""

    def test_default_fallback(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.chdir(tmp_path)
        result = _resolve_spec_file()
        assert result == Path("specs/models.py")

    def test_custom_fallback(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.chdir(tmp_path)
        result = _resolve_spec_file("custom/spec.py")
        assert result == Path("custom/spec.py")

    def test_finds_existing_spec(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.chdir(tmp_path)
        spec_dir = tmp_path / "specs"
        spec_dir.mkdir()
        spec_file = spec_dir / "models.py"
        spec_file.write_text("spec = None")
        result = _resolve_spec_file()
        assert result == Path("specs/models.py")

    def test_finds_spec_py(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.chdir(tmp_path)
        spec_file = tmp_path / "spec.py"
        spec_file.write_text("spec = None")
        result = _resolve_spec_file()
        assert result == Path("spec.py")

    def test_from_prisme_toml(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.chdir(tmp_path)
        toml_file = tmp_path / "prisme.toml"
        toml_file.write_text('prisme_version = "0.12.1"\n\n[project]\nspec_path = "my/spec.py"\n')
        spec_file = tmp_path / "my" / "spec.py"
        spec_file.parent.mkdir(parents=True)
        spec_file.write_text("spec = None")
        result = _resolve_spec_file()
        assert result == Path("my/spec.py")


class TestMainGroup:
    """Tests for the main CLI group."""

    def test_version(self, cli_runner: CliRunner) -> None:
        result = cli_runner.invoke(main, ["--version"])
        assert result.exit_code == 0
        assert "version" in result.output.lower() or "prisme" in result.output.lower()

    def test_help(self, cli_runner: CliRunner) -> None:
        result = cli_runner.invoke(main, ["--help"])
        assert result.exit_code == 0
        assert "Prism" in result.output

    def test_validate_help(self, cli_runner: CliRunner) -> None:
        result = cli_runner.invoke(main, ["validate", "--help"])
        assert result.exit_code == 0
        assert "Validate" in result.output

    def test_generate_help(self, cli_runner: CliRunner) -> None:
        result = cli_runner.invoke(main, ["generate", "--help"])
        assert result.exit_code == 0

    def test_create_help(self, cli_runner: CliRunner) -> None:
        result = cli_runner.invoke(main, ["create", "--help"])
        assert result.exit_code == 0
        assert "Create" in result.output

    def test_db_help(self, cli_runner: CliRunner) -> None:
        result = cli_runner.invoke(main, ["db", "--help"])
        assert result.exit_code == 0
        assert "Database" in result.output

    def test_schema_help(self, cli_runner: CliRunner) -> None:
        result = cli_runner.invoke(main, ["schema", "--help"])
        assert result.exit_code == 0

    def test_plan_help(self, cli_runner: CliRunner) -> None:
        result = cli_runner.invoke(main, ["plan", "--help"])
        assert result.exit_code == 0

    def test_apply_help(self, cli_runner: CliRunner) -> None:
        result = cli_runner.invoke(main, ["apply", "--help"])
        assert result.exit_code == 0

    def test_review_help(self, cli_runner: CliRunner) -> None:
        result = cli_runner.invoke(main, ["review", "--help"])
        assert result.exit_code == 0

    def test_doctor_help(self, cli_runner: CliRunner) -> None:
        result = cli_runner.invoke(main, ["doctor", "--help"])
        assert result.exit_code == 0

    def test_docker_help(self, cli_runner: CliRunner) -> None:
        result = cli_runner.invoke(main, ["docker", "--help"])
        assert result.exit_code == 0


class TestValidateCommand:
    """Tests for the validate command."""

    def test_validate_valid_spec(self, cli_runner: CliRunner, tmp_path: Path) -> None:
        spec_file = tmp_path / "models.py"
        spec_file.write_text(
            """\
from prisme.spec.stack import StackSpec
from prisme.spec.model import ModelSpec
from prisme.spec.fields import FieldSpec, FieldType

spec = StackSpec(
    name="test",
    version="1.0.0",
    models=[
        ModelSpec(
            name="Item",
            fields=[FieldSpec(name="name", type=FieldType.STRING, required=True)],
        )
    ],
)
"""
        )
        result = cli_runner.invoke(main, ["validate", str(spec_file)])
        assert result.exit_code == 0
        assert "valid" in result.output.lower()

    def test_validate_nonexistent_spec(self, cli_runner: CliRunner, tmp_path: Path) -> None:
        result = cli_runner.invoke(main, ["validate", str(tmp_path / "nonexistent.py")])
        assert result.exit_code != 0

    def test_validate_bad_spec(self, cli_runner: CliRunner, tmp_path: Path) -> None:
        spec_file = tmp_path / "bad.py"
        spec_file.write_text("x = 42")
        result = cli_runner.invoke(main, ["validate", str(spec_file)])
        assert result.exit_code != 0


class TestSchemaCommand:
    """Tests for the schema command."""

    def test_schema_with_valid_spec(self, cli_runner: CliRunner, tmp_path: Path) -> None:
        spec_file = tmp_path / "models.py"
        spec_file.write_text(
            """\
from prisme.spec.stack import StackSpec
from prisme.spec.model import ModelSpec
from prisme.spec.fields import FieldSpec, FieldType

spec = StackSpec(
    name="test",
    version="1.0.0",
    models=[
        ModelSpec(
            name="Item",
            fields=[FieldSpec(name="name", type=FieldType.STRING, required=True)],
        )
    ],
)
"""
        )
        result = cli_runner.invoke(main, ["schema", str(spec_file)])
        assert result.exit_code == 0

    def test_schema_with_output_file(self, cli_runner: CliRunner, tmp_path: Path) -> None:
        spec_file = tmp_path / "models.py"
        spec_file.write_text(
            """\
from prisme.spec.stack import StackSpec
from prisme.spec.model import ModelSpec
from prisme.spec.fields import FieldSpec, FieldType

spec = StackSpec(
    name="test",
    version="1.0.0",
    models=[
        ModelSpec(
            name="Item",
            fields=[FieldSpec(name="name", type=FieldType.STRING, required=True)],
        )
    ],
)
"""
        )
        output_file = tmp_path / "schema.graphql"
        result = cli_runner.invoke(main, ["schema", str(spec_file), "--output", str(output_file)])
        assert result.exit_code == 0
        assert output_file.exists()


class TestPlanCommand:
    """Tests for the plan command."""

    def test_plan_with_spec(
        self, cli_runner: CliRunner, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        monkeypatch.chdir(tmp_path)
        spec_dir = tmp_path / "specs"
        spec_dir.mkdir()
        spec_file = spec_dir / "models.py"
        spec_file.write_text(
            """\
from prisme.spec.stack import StackSpec
from prisme.spec.model import ModelSpec
from prisme.spec.fields import FieldSpec, FieldType

spec = StackSpec(
    name="test",
    version="1.0.0",
    models=[
        ModelSpec(
            name="Item",
            fields=[FieldSpec(name="name", type=FieldType.STRING, required=True)],
        )
    ],
)
"""
        )
        result = cli_runner.invoke(main, ["plan", "--spec", str(spec_file)])
        assert result.exit_code == 0

    def test_plan_with_nonexistent_spec(
        self, cli_runner: CliRunner, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        monkeypatch.chdir(tmp_path)
        result = cli_runner.invoke(main, ["plan", "--spec-path", "nonexistent.py"])
        assert result.exit_code != 0


class TestApplyCommand:
    """Tests for the apply command."""

    def test_apply_without_plan(
        self, cli_runner: CliRunner, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        monkeypatch.chdir(tmp_path)
        result = cli_runner.invoke(main, ["apply"])
        assert result.exit_code != 0


class TestDoctorCommand:
    """Tests for the doctor command."""

    def test_doctor_runs(
        self, cli_runner: CliRunner, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        monkeypatch.chdir(tmp_path)
        result = cli_runner.invoke(main, ["doctor"])
        # Doctor should run (even if checks fail in empty dir), exit 1 is expected
        assert result.exit_code in (0, 1)
        assert "doctor" in result.output.lower()


class TestDbInitCommand:
    """Tests for the db init command."""

    def test_db_init_alembic_exists(
        self, cli_runner: CliRunner, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        monkeypatch.chdir(tmp_path)
        (tmp_path / "alembic.ini").write_text("[alembic]")
        result = cli_runner.invoke(main, ["db", "init"])
        assert result.exit_code == 0
        assert "already initialized" in result.output.lower()


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

    def test_migrate_help(self, cli_runner: CliRunner) -> None:
        result = cli_runner.invoke(main, ["migrate", "--help"])
        assert result.exit_code == 0
