"""Tests for contract schema validation."""

import pytest

from entr_adapter_core.schemas import (
    AdapterResponse,
    AppliedMapping,
    DocumentPayload,
    ProposedValue,
    ReviewSuggestion,
    StatusEnum,
)


class TestStatusEnum:
    """Tests for StatusEnum values."""

    def test_contains_exactly_three_values(self):
        values = {e.value for e in StatusEnum}
        assert values == {"success", "failed", "review_required"}

    def test_success_value(self):
        assert StatusEnum.SUCCESS.value == "success"

    def test_failed_value(self):
        assert StatusEnum.FAILED.value == "failed"

    def test_review_required_value(self):
        assert StatusEnum.REVIEW_REQUIRED.value == "review_required"


class TestAppliedMapping:
    """Tests for AppliedMapping model."""

    def test_valid_instance(self):
        mapping = AppliedMapping(
            source_value="ACME Corp",
            target_value="Acme Corporation",
            mapping_field="customer.company_name",
        )
        assert mapping.source_value == "ACME Corp"
        assert mapping.target_value == "Acme Corporation"
        assert mapping.mapping_field == "customer.company_name"

    def test_all_fields_required(self):
        with pytest.raises(Exception):
            AppliedMapping(source_value="a", target_value="b")

    def test_round_trip_serialization(self):
        data = {
            "source_value": "old",
            "target_value": "new",
            "mapping_field": "field",
        }
        mapping = AppliedMapping.model_validate(data)
        assert mapping.model_dump() == data


class TestDocumentPayload:
    """Tests for DocumentPayload model."""

    def test_required_fields_only(self):
        payload = DocumentPayload(
            adapter_identifier="purchase_order",
            document_id=123,
            data={"invoice_number": "INV-001"},
        )
        assert payload.adapter_identifier == "purchase_order"
        assert payload.document_id == 123
        assert payload.data == {"invoice_number": "INV-001"}
        assert payload.original_data is None
        assert payload.applied_mappings is None
        assert payload.original_file_content_b64 is None
        assert payload.original_file_name is None

    def test_all_optional_fields(self):
        payload = DocumentPayload(
            adapter_identifier="purchase_order",
            document_id=456,
            data={"total": 100.0},
            original_data={"total": 99.0},
            applied_mappings=[
                AppliedMapping(
                    source_value="old",
                    target_value="new",
                    mapping_field="customer",
                )
            ],
            original_file_content_b64="dGVzdA==",
            original_file_name="invoice.pdf",
        )
        assert payload.original_data == {"total": 99.0}
        assert len(payload.applied_mappings) == 1
        assert payload.original_file_content_b64 == "dGVzdA=="
        assert payload.original_file_name == "invoice.pdf"

    def test_does_not_have_removed_fields(self):
        payload = DocumentPayload(
            adapter_identifier="test",
            document_id=1,
            data={},
        )
        assert not hasattr(payload, "meta")
        assert not hasattr(payload, "context")
        assert not hasattr(payload, "mappings")
        assert not hasattr(payload, "mapping_rules")

    def test_round_trip_serialization(self):
        data = {
            "adapter_identifier": "test",
            "document_id": 1,
            "data": {"key": "value"},
        }
        payload = DocumentPayload.model_validate(data)
        dumped = payload.model_dump()
        assert dumped["adapter_identifier"] == "test"
        assert dumped["document_id"] == 1
        assert dumped["data"] == {"key": "value"}


class TestProposedValue:
    """Tests for ProposedValue model."""

    def test_valid_instance(self):
        pv = ProposedValue(value="P-12345", description="Product ID match")
        assert pv.value == "P-12345"
        assert pv.description == "Product ID match"

    def test_round_trip_serialization(self):
        data = {"value": 42, "description": "Numeric suggestion"}
        pv = ProposedValue.model_validate(data)
        assert pv.model_dump() == data


class TestReviewSuggestion:
    """Tests for ReviewSuggestion model."""

    def test_valid_instance(self):
        suggestion = ReviewSuggestion(
            field_path="line_items.0.product_id",
            original_value="P-123",
            proposed_values=[
                ProposedValue(value="P-12345", description="Exact match"),
                ProposedValue(value="P-12346", description="Fuzzy match"),
            ],
            reason="Fuzzy match on product code",
        )
        assert suggestion.field_path == "line_items.0.product_id"
        assert len(suggestion.proposed_values) == 2
        assert suggestion.reason == "Fuzzy match on product code"

    def test_reason_optional(self):
        suggestion = ReviewSuggestion(
            field_path="field",
            original_value="old",
            proposed_values=[ProposedValue(value="new", description="desc")],
        )
        assert suggestion.reason is None

    def test_proposed_values_validates_as_list_of_proposed_value(self):
        suggestion = ReviewSuggestion(
            field_path="field",
            original_value="x",
            proposed_values=[
                ProposedValue(value="a", description="option a"),
            ],
        )
        assert all(isinstance(pv, ProposedValue) for pv in suggestion.proposed_values)

    def test_round_trip_serialization(self):
        data = {
            "field_path": "total",
            "original_value": 100,
            "proposed_values": [{"value": 110, "description": "corrected"}],
            "reason": "rounding",
        }
        suggestion = ReviewSuggestion.model_validate(data)
        dumped = suggestion.model_dump()
        assert dumped["field_path"] == "total"
        assert len(dumped["proposed_values"]) == 1


class TestAdapterResponse:
    """Tests for AdapterResponse model."""

    def test_required_fields_with_success(self):
        resp = AdapterResponse(
            document_id=123,
            status=StatusEnum.SUCCESS,
        )
        assert resp.document_id == 123
        assert resp.status == StatusEnum.SUCCESS

    def test_required_fields_with_failed(self):
        resp = AdapterResponse(
            document_id=456,
            status=StatusEnum.FAILED,
            failure_type="validation_error",
            failure_reason="Missing required field",
        )
        assert resp.status == StatusEnum.FAILED
        assert resp.failure_type == "validation_error"

    def test_required_fields_with_review_required(self):
        resp = AdapterResponse(
            document_id=789,
            status=StatusEnum.REVIEW_REQUIRED,
            review_reason="Ambiguous product match",
            review_suggestions=[
                ReviewSuggestion(
                    field_path="product_id",
                    original_value="P-1",
                    proposed_values=[ProposedValue(value="P-100", description="match")],
                )
            ],
        )
        assert resp.status == StatusEnum.REVIEW_REQUIRED
        assert len(resp.review_suggestions) == 1

    def test_does_not_have_removed_fields(self):
        resp = AdapterResponse(document_id=1, status=StatusEnum.SUCCESS)
        assert not hasattr(resp, "created_mappings")
        assert not hasattr(resp, "generated_mappings")

    def test_notifications_field(self):
        resp = AdapterResponse(
            document_id=1,
            status=StatusEnum.SUCCESS,
            notifications=["Document processed successfully", "3 line items matched"],
        )
        assert len(resp.notifications) == 2

    def test_execution_logs_field(self):
        resp = AdapterResponse(
            document_id=1,
            status=StatusEnum.SUCCESS,
            execution_logs=[
                {"step": "validate", "duration_ms": 50},
                {"step": "transform", "duration_ms": 120},
            ],
        )
        assert len(resp.execution_logs) == 2

    def test_round_trip_serialization(self):
        data = {
            "document_id": 1,
            "status": "success",
            "failure_type": None,
            "failure_reason": None,
            "details": None,
            "review_reason": None,
            "review_suggestions": None,
            "notifications": None,
            "execution_logs": None,
        }
        resp = AdapterResponse.model_validate(data)
        dumped = resp.model_dump()
        assert dumped["document_id"] == 1
        assert dumped["status"] == "success"
