# #region Test.Schemas.Validation [C:2] [TYPE Module] [SEMANTICS test,schema,validation,task,run]
# @BRIEF Tests for schemas/validation.py — SourceInput, SourceResponse, ValidationTaskCreate,
#        ValidationTaskUpdate, ValidationTaskResponse, ValidationTaskListResponse,
#        TriggerRunResponse, RunResponse, RunListResponse, RecordResponse,
#        RecordListResponse, RunDetailResponse.
# @RELATION BINDS_TO -> [ValidationSchemas]
# @TEST_EDGE: missing_field -> optional fields default correctly
# @TEST_EDGE: invalid_type -> Literal constraints reject bad values
# @TEST_EDGE: serialization -> round-trip via model_dump/model_validate

from datetime import datetime, timezone
import pytest
from pydantic import ValidationError

from src.schemas.validation import (
    RecordListResponse,
    RecordResponse,
    RunDetailResponse,
    RunListResponse,
    RunResponse,
    SourceInput,
    SourceResponse,
    TriggerRunResponse,
    ValidationTaskCreate,
    ValidationTaskListResponse,
    ValidationTaskResponse,
    ValidationTaskUpdate,
)


class TestSourceInput:
    """SourceInput — Literal type and required value."""

    def test_dashboard_id(self):
        si = SourceInput(type="dashboard_id", value="42")
        assert si.type == "dashboard_id"
        assert si.value == "42"

    def test_dashboard_url(self):
        si = SourceInput(type="dashboard_url", value="http://superset/dash/42")
        assert si.type == "dashboard_url"

    def test_invalid_type_raises(self):
        with pytest.raises(ValidationError):
            SourceInput(type="invalid", value="x")

    def test_serialize_roundtrip(self):
        si = SourceInput(type="dashboard_id", value="42")
        data = si.model_dump()
        restored = SourceInput.model_validate(data)
        assert restored.type == "dashboard_id"
        assert restored.value == "42"


class TestSourceResponse:
    """SourceResponse — validation source with metadata and from_attributes."""

    def test_minimal(self):
        sr = SourceResponse(id="s-1", type="dashboard_id", value="42")
        assert sr.status == "valid"
        assert sr.title is None
        assert sr.resolved_dashboard_id is None
        assert sr.last_error is None
        assert sr.last_checked_at is None
        assert sr.created_at is None

    def test_with_errors(self):
        sr = SourceResponse(id="s-1", type="dashboard_url", value="http://x", status="error", last_error="Invalid")
        assert sr.status == "error"
        assert sr.last_error == "Invalid"

    def test_serialize_roundtrip(self):
        sr = SourceResponse(id="s-1", type="dashboard_id", value="42", title="Sales")
        data = sr.model_dump()
        restored = SourceResponse.model_validate(data)
        assert restored.title == "Sales"

    def test_from_attributes(self):
        class FakeORM:
            id = "s-1"
            type = "dashboard_id"
            value = "42"
            status = "valid"
            title = "Sales"
            resolved_dashboard_id = "42"
            last_error = None
            last_checked_at = None
            created_at = None
        sr = SourceResponse.model_validate(FakeORM())
        assert sr.id == "s-1"
        assert sr.resolved_dashboard_id == "42"


class TestValidationTaskCreate:
    """ValidationTaskCreate — create task with v1/v2 fields."""

    def test_minimal(self):
        task = ValidationTaskCreate(
            name="Test Task",
            environment_id="env-1",
            provider_id="prov-1",
        )
        assert task.description is None
        assert task.dashboard_ids == []
        assert task.is_active is True
        assert task.screenshot_enabled is True
        assert task.logs_enabled is True
        assert task.execute_chart_data is False
        assert task.llm_batch_size == 1
        assert task.policy_dashboard_concurrency_limit == 3
        assert task.sources is None

    def test_with_all_fields(self):
        sources = [SourceInput(type="dashboard_id", value="42")]
        task = ValidationTaskCreate(
            name="Full Task",
            description="Full description",
            environment_id="env-1",
            dashboard_ids=["42", "99"],
            provider_id="prov-1",
            schedule_days=[0, 2, 4],
            window_start="09:00",
            window_end="18:00",
            notify_owners=False,
            custom_channels=["slack:#alerts"],
            alert_condition="WARN_AND_FAIL",
            is_active=False,
            screenshot_enabled=False,
            logs_enabled=False,
            execute_chart_data=True,
            llm_batch_size=5,
            policy_dashboard_concurrency_limit=10,
            prompt_template="Custom template",
            sources=sources,
        )
        assert task.name == "Full Task"
        assert task.schedule_days == [0, 2, 4]
        assert task.execute_chart_data is True
        assert task.llm_batch_size == 5
        assert task.sources == sources

    def test_llm_batch_size_ge_one(self):
        with pytest.raises(ValidationError):
            ValidationTaskCreate(name="T", environment_id="e1", provider_id="p1", llm_batch_size=0)

    def test_concurrency_limit_ge_one(self):
        with pytest.raises(ValidationError):
            ValidationTaskCreate(name="T", environment_id="e1", provider_id="p1", policy_dashboard_concurrency_limit=0)

    def test_serialize_roundtrip(self):
        task = ValidationTaskCreate(name="T", environment_id="e1", provider_id="p1")
        data = task.model_dump()
        restored = ValidationTaskCreate.model_validate(data)
        assert restored.name == "T"
        assert restored.is_active is True


class TestValidationTaskUpdate:
    """ValidationTaskUpdate — all fields optional for partial update."""

    def test_empty(self):
        u = ValidationTaskUpdate()
        assert u.name is None
        assert u.description is None
        assert u.environment_id is None
        assert u.dashboard_ids is None
        assert u.provider_id is None
        assert u.schedule_days is None
        assert u.is_active is None
        assert u.sources is None

    def test_partial(self):
        u = ValidationTaskUpdate(name="Renamed", is_active=False)
        assert u.name == "Renamed"
        assert u.is_active is False

    def test_serialize_roundtrip(self):
        u = ValidationTaskUpdate(name="New", screenshot_enabled=False)
        data = u.model_dump()
        restored = ValidationTaskUpdate.model_validate(data)
        assert restored.name == "New"
        assert restored.screenshot_enabled is False


class TestValidationTaskResponse:
    """ValidationTaskResponse — full task response with from_attributes."""

    def test_minimal(self):
        ts = datetime.now(timezone.utc)
        task = ValidationTaskResponse(
            id="task-1",
            name="My Task",
            environment_id="env-1",
            is_active=True,
            dashboard_ids=["42"],
            created_at=ts,
        )
        assert task.description is None
        assert task.provider_id is None
        assert task.last_run_status is None
        assert task.screenshot_enabled is True
        assert task.llm_batch_size == 1
        assert task.sources == []

    def test_with_all_fields(self):
        ts = datetime.now(timezone.utc)
        sources = [SourceResponse(id="s-1", type="dashboard_id", value="42")]
        task = ValidationTaskResponse(
            id="task-1", name="Full", environment_id="env-1", is_active=True,
            dashboard_ids=["42"], provider_id="prov-1",
            schedule_days=[1], window_start="09:00", window_end="18:00",
            notify_owners=False, custom_channels=["email"], alert_condition="ALWAYS",
            created_at=ts, updated_at=ts,
            last_run_status="PASS", last_run_at=ts,
            last_run_summary={"pass": 5, "fail": 0},
            screenshot_enabled=False, logs_enabled=True, execute_chart_data=True,
            llm_batch_size=3, policy_dashboard_concurrency_limit=5,
            prompt_template="custom", sources=sources,
        )
        assert task.last_run_status == "PASS"
        assert task.last_run_summary == {"pass": 5, "fail": 0}
        assert len(task.sources) == 1

    def test_serialize_roundtrip(self):
        ts = datetime.now(timezone.utc)
        task = ValidationTaskResponse(id="t-1", name="T", environment_id="e1", is_active=True, dashboard_ids=[], created_at=ts)
        data = task.model_dump()
        restored = ValidationTaskResponse.model_validate(data)
        assert restored.name == "T"

    def test_from_attributes(self):
        ts = datetime.now(timezone.utc)
        class FakeORM:
            id = "t-1"
            name = "Task"
            description = "Desc"
            environment_id = "e1"
            is_active = True
            dashboard_ids = ["42"]
            provider_id = "p1"
            schedule_days = [1]
            window_start = "09:00"
            window_end = "18:00"
            notify_owners = True
            custom_channels = None
            alert_condition = "FAIL_ONLY"
            created_at = ts
            updated_at = ts
            last_run_status = "PASS"
            last_run_at = ts
            last_run_summary = None
            screenshot_enabled = True
            logs_enabled = True
            execute_chart_data = False
            llm_batch_size = 1
            policy_dashboard_concurrency_limit = 3
            prompt_template = None
            sources = []
        task = ValidationTaskResponse.model_validate(FakeORM())
        assert task.name == "Task"
        assert task.provider_id == "p1"
        assert task.screenshot_enabled is True


class TestValidationTaskListResponse:
    """ValidationTaskListResponse — paginated list."""

    def test_defaults(self):
        resp = ValidationTaskListResponse()
        assert resp.items == []
        assert resp.total == 0
        assert resp.page == 1
        assert resp.page_size == 20

    def test_with_items(self):
        ts = datetime.now(timezone.utc)
        items = [ValidationTaskResponse(id="t-1", name="T", environment_id="e1", is_active=True, dashboard_ids=[], created_at=ts)]
        resp = ValidationTaskListResponse(items=items, total=1, page=1, page_size=10)
        assert len(resp.items) == 1
        assert resp.total == 1

    def test_serialize_roundtrip(self):
        resp = ValidationTaskListResponse(total=5, page=2, page_size=10)
        data = resp.model_dump()
        restored = ValidationTaskListResponse.model_validate(data)
        assert restored.total == 5
        assert restored.page == 2


class TestTriggerRunResponse:
    """TriggerRunResponse — spawned task response."""

    def test_minimal(self):
        resp = TriggerRunResponse(task_id="t-1", spawned_task_id="st-1")
        assert resp.run_id is None
        assert resp.status == "PENDING"
        assert resp.message == "Validation task spawned"

    def test_with_run_id(self):
        resp = TriggerRunResponse(task_id="t-1", spawned_task_id="st-1", run_id="run-1", status="RUNNING")
        assert resp.run_id == "run-1"
        assert resp.status == "RUNNING"

    def test_serialize_roundtrip(self):
        resp = TriggerRunResponse(task_id="t-1", spawned_task_id="st-1")
        data = resp.model_dump()
        restored = TriggerRunResponse.model_validate(data)
        assert restored.task_id == "t-1"
        assert restored.spawned_task_id == "st-1"


class TestRunResponse:
    """RunResponse — v2 ValidationRun display."""

    def test_minimal(self):
        resp = RunResponse(id="run-1", policy_id="pol-1", status="PENDING")
        assert resp.task_id is None
        assert resp.trigger == "manual"
        assert resp.started_at is None
        assert resp.finished_at is None
        assert resp.dashboard_count == 0
        assert resp.created_at is None

    def test_with_all_fields(self):
        ts = datetime.now(timezone.utc)
        resp = RunResponse(
            id="run-1", policy_id="pol-1", task_id="t-1", status="RUNNING",
            trigger="scheduled", started_at=ts, finished_at=None,
            dashboard_count=5, pass_count=3, warn_count=1, fail_count=1,
            unknown_count=0, created_at=ts,
        )
        assert resp.trigger == "scheduled"
        assert resp.pass_count == 3

    def test_serialize_roundtrip(self):
        resp = RunResponse(id="r-1", policy_id="p-1", status="COMPLETED")
        data = resp.model_dump()
        restored = RunResponse.model_validate(data)
        assert restored.status == "COMPLETED"

    def test_from_attributes(self):
        class FakeORM:
            id = "r-1"
            policy_id = "p-1"
            task_id = "t-1"
            status = "RUNNING"
            trigger = "manual"
            started_at = None
            finished_at = None
            dashboard_count = 10
            pass_count = 8
            warn_count = 1
            fail_count = 1
            unknown_count = 0
            created_at = None
        resp = RunResponse.model_validate(FakeORM())
        assert resp.dashboard_count == 10


class TestRunListResponse:
    """RunListResponse — paginated run list."""

    def test_defaults(self):
        resp = RunListResponse()
        assert resp.items == []
        assert resp.total == 0
        assert resp.page == 1
        assert resp.page_size == 20

    def test_serialize_roundtrip(self):
        resp = RunListResponse(total=3, page=1, page_size=10)
        data = resp.model_dump()
        restored = RunListResponse.model_validate(data)
        assert restored.total == 3


class TestRecordResponse:
    """RecordResponse — validation record with lots of optional fields."""

    def test_minimal(self):
        ts = datetime.now(timezone.utc)
        resp = RecordResponse(id="rec-1", dashboard_id="42", status="PASS", timestamp=ts)
        assert resp.policy_id is None
        assert resp.run_id is None
        assert resp.dashboard_title is None
        assert resp.summary == ""
        assert resp.issues == []
        assert resp.screenshot_paths == []

    def test_with_all_fields(self):
        ts = datetime.now(timezone.utc)
        resp = RecordResponse(
            id="rec-1", policy_id="pol-1", run_id="run-1", dashboard_id="42",
            dashboard_title="Sales", environment_id="env-1", status="FAIL",
            summary="Error", timestamp=ts, execution_path="screenshot",
            screenshot_path="/screens/1.png", screenshot_paths=["/screens/1.png"],
            issues=[{"severity": "FAIL", "message": "broken"}],
            raw_response='{"ok": true}', dataset_health={"status": "OK"},
            chart_data_results=[{"chart": "c1"}],
            tab_screenshots=[{"tab": "tab1"}],
            logs_sent_to_llm=["log1"], token_usage={"prompt": 100},
            timings={"total": 5.0},
        )
        assert resp.dashboard_title == "Sales"
        assert len(resp.issues) == 1
        assert resp.timings == {"total": 5.0}

    def test_serialize_roundtrip(self):
        ts = datetime.now(timezone.utc)
        resp = RecordResponse(id="rec-1", dashboard_id="42", status="PASS", timestamp=ts)
        data = resp.model_dump()
        restored = RecordResponse.model_validate(data)
        assert restored.dashboard_id == "42"

    def test_from_attributes(self):
        ts = datetime.now(timezone.utc)
        class FakeORM:
            id = "rec-1"
            policy_id = "p-1"
            run_id = "r-1"
            dashboard_id = "42"
            dashboard_title = "Sales"
            environment_id = "e1"
            status = "WARN"
            summary = "Warning"
            timestamp = ts
            execution_path = None
            screenshot_path = None
            screenshot_paths = []
            issues = []
            raw_response = None
            dataset_health = None
            chart_data_results = []
            tab_screenshots = []
            logs_sent_to_llm = []
            token_usage = None
            timings = None
        resp = RecordResponse.model_validate(FakeORM())
        assert resp.dashboard_title == "Sales"
        assert resp.status == "WARN"


class TestRecordListResponse:
    """RecordListResponse — paginated record list."""

    def test_defaults(self):
        resp = RecordListResponse()
        assert resp.items == []
        assert resp.total == 0
        assert resp.page == 1
        assert resp.page_size == 20

    def test_serialize_roundtrip(self):
        resp = RecordListResponse(total=10, page=1, page_size=25)
        data = resp.model_dump()
        restored = RecordListResponse.model_validate(data)
        assert restored.total == 10


class TestRunDetailResponse:
    """RunDetailResponse — run with records."""

    def test_minimal(self):
        run = RunResponse(id="r-1", policy_id="p-1", status="COMPLETED")
        resp = RunDetailResponse(run=run)
        assert resp.records == []

    def test_with_records(self):
        run = RunResponse(id="r-1", policy_id="p-1", status="COMPLETED")
        resp = RunDetailResponse(run=run, records=[{"id": "rec-1"}, {"id": "rec-2"}])
        assert len(resp.records) == 2

    def test_serialize_roundtrip(self):
        run = RunResponse(id="r-1", policy_id="p-1", status="COMPLETED")
        resp = RunDetailResponse(run=run)
        data = resp.model_dump()
        restored = RunDetailResponse.model_validate(data)
        assert restored.run.id == "r-1"
# #endregion Test.Schemas.Validation
