"""Tests for exokit Pydantic models — validation, defaults, serialization."""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from exokit.core.models import (
    Decision,
    DemoManifest,
    ManifestMeta,
    PrereqResult,
    Question,
    QuestionnaireAnswers,
    ScaffoldContext,
    ScaffoldResult,
    SchemaDefinition,
    WaveDefinition,
)


class TestQuestion:
    """Tests for the Question model."""

    def test_text_question(self) -> None:
        q = Question(id="company", type="text", prompt="Company name")
        assert q.id == "company"
        assert q.type == "text"
        assert q.required is True
        assert q.default is None
        assert q.default_from is None
        assert q.options is None

    def test_select_question(self) -> None:
        q = Question(
            id="industry",
            type="select",
            prompt="Industry",
            options=["fsi", "telecom", "retail"],
        )
        assert q.options == ["fsi", "telecom", "retail"]
        assert q.type == "select"

    def test_question_with_default_from(self) -> None:
        q = Question(
            id="catalog",
            type="text",
            prompt="Catalog",
            default_from="catalog_from_company_industry",
        )
        assert q.default_from == "catalog_from_company_industry"
        assert q.default is None

    def test_question_with_static_default(self) -> None:
        q = Question(
            id="profile",
            type="text",
            prompt="Profile",
            required=False,
            default="DEFAULT",
        )
        assert q.default == "DEFAULT"
        assert q.required is False

    def test_question_json_round_trip(self) -> None:
        q = Question(
            id="company",
            type="text",
            prompt="Company name",
            help_text="Enter company",
        )
        data = q.model_dump_json()
        restored = Question.model_validate_json(data)
        assert restored == q

    def test_question_invalid_type(self) -> None:
        with pytest.raises(Exception):  # noqa: B017
            Question(id="bad", type="radio", prompt="Bad type")  # type: ignore[arg-type]


class TestQuestionnaireAnswers:
    """Tests for QuestionnaireAnswers model."""

    def test_valid_answers(self, sample_answers: QuestionnaireAnswers) -> None:
        assert sample_answers.company == "Acme Bank"
        assert sample_answers.industry == "fsi"
        assert sample_answers.databricks_profile == "DEFAULT"
        assert sample_answers.lakebase_url is None

    def test_minimal_answers(self) -> None:
        answers = QuestionnaireAnswers(
            company="Test",
            industry="retail",
            demo_description="Test demo description",
            catalog="test_retail",
            warehouse_id="wh123",
            workspace_host="https://test.databricks.net",
            notification_email="test@test.com",
        )
        assert answers.databricks_profile is None
        assert answers.lakebase_url is None

    def test_missing_required_field(self) -> None:
        with pytest.raises(Exception):  # noqa: B017
            QuestionnaireAnswers(
                company="Test",
                industry="retail",
                demo_description="Test demo description",
                # missing catalog, warehouse_id, workspace_host, notification_email
            )  # type: ignore[call-arg]

    def test_answers_json_round_trip(self, sample_answers: QuestionnaireAnswers) -> None:
        data = sample_answers.model_dump_json()
        restored = QuestionnaireAnswers.model_validate_json(data)
        assert restored == sample_answers


class TestScaffoldContext:
    """Tests for ScaffoldContext (answers + computed fields)."""

    def test_context_has_computed_fields(self, sample_context: ScaffoldContext) -> None:
        assert sample_context.bundle_name == "acme-bank-fsi-demo"
        assert sample_context.app_name == "acme-bank-fsi-app"

    def test_context_inherits_answers(self, sample_context: ScaffoldContext) -> None:
        assert sample_context.company == "Acme Bank"
        assert sample_context.industry == "fsi"
        assert sample_context.warehouse_id == "abc123def456"

    def test_context_json_round_trip(self, sample_context: ScaffoldContext) -> None:
        data = sample_context.model_dump_json()
        restored = ScaffoldContext.model_validate_json(data)
        assert restored == sample_context

    def test_context_is_superset_of_answers(self) -> None:
        """ScaffoldContext must contain all QuestionnaireAnswers fields."""
        answer_fields = set(QuestionnaireAnswers.model_fields.keys())
        context_fields = set(ScaffoldContext.model_fields.keys())
        assert answer_fields.issubset(context_fields)


class TestPrereqResult:
    """Tests for PrereqResult model."""

    def test_passing_result(self) -> None:
        result = PrereqResult(
            name="uv",
            passed=True,
            message="uv found: uv 0.5.1",
            version="0.5.1",
        )
        assert result.passed is True
        assert result.version == "0.5.1"

    def test_failing_result(self) -> None:
        result = PrereqResult(
            name="apx",
            passed=False,
            message="APX not found",
        )
        assert result.passed is False
        assert result.version is None


class TestWaveDefinition:
    """Tests for WaveDefinition model."""

    def test_default_status(self) -> None:
        wave = WaveDefinition(
            id=1,
            name="Data Foundation",
            components=["data_gen"],
            description="Generate data",
        )
        assert wave.status == "pending"

    def test_all_statuses(self) -> None:
        for status in ("pending", "in_progress", "completed", "skipped"):
            wave = WaveDefinition(
                id=1,
                name="Test",
                status=status,  # type: ignore[arg-type]
                components=[],
                description="Test",
            )
            assert wave.status == status


class TestSchemaDefinition:
    """Tests for SchemaDefinition model."""

    def test_empty_default(self) -> None:
        schema = SchemaDefinition()
        assert schema.tables == {}
        assert schema.notes == ""

    def test_with_tables(self) -> None:
        schema = SchemaDefinition(
            tables={"customers": ["id", "name", "email"]},
            notes="Core customer table",
        )
        assert "customers" in schema.tables
        assert len(schema.tables["customers"]) == 3


class TestDecision:
    """Tests for Decision model."""

    def test_minimal_decision(self) -> None:
        d = Decision(summary="Use DLT for pipelines")
        assert d.summary == "Use DLT for pipelines"
        assert d.rationale == ""
        assert d.decided_at == ""

    def test_full_decision(self) -> None:
        d = Decision(
            summary="Use DLT for pipelines",
            rationale="Declarative, auto-scaling, built-in quality checks",
            decided_at="2026-03-02",
        )
        assert d.rationale != ""


class TestDemoManifest:
    """Tests for DemoManifest model."""

    def test_manifest_structure(self, sample_manifest: DemoManifest) -> None:
        assert sample_manifest.meta.company == "Acme Bank"
        assert len(sample_manifest.waves) == 2
        assert sample_manifest.waves[0].name == "Data Foundation"

    def test_manifest_defaults(self) -> None:
        manifest = DemoManifest(
            meta=ManifestMeta(
                company="Test",
                industry="retail",
                demo_description="Test demo description",
                generated_at="2026-01-01T00:00:00Z",
                exokit_version="0.1.0",
            ),
            waves=[],
        )
        assert manifest.schemas.tables == {}
        assert manifest.talk_track_hints == []
        assert manifest.decisions == []

    def test_manifest_json_round_trip(self, sample_manifest: DemoManifest) -> None:
        json_str = sample_manifest.model_dump_json()
        parsed = json.loads(json_str)
        assert parsed["meta"]["company"] == "Acme Bank"
        restored = DemoManifest.model_validate_json(json_str)
        assert restored == sample_manifest


class TestScaffoldResult:
    """Tests for ScaffoldResult model."""

    def test_scaffold_result(self, sample_manifest: DemoManifest) -> None:
        result = ScaffoldResult(
            project_dir=Path("/tmp/test-project"),
            files_created=["CLAUDE.md", "databricks.yml"],
            warnings=["APX not installed"],
            manifest=sample_manifest,
        )
        assert result.project_dir == Path("/tmp/test-project")
        assert len(result.files_created) == 2
        assert len(result.warnings) == 1
