from datetime import UTC, date, datetime

import pytest

from zbt.core.errors import AppError
from zbt.domains.enrollment.models import Policy
from zbt.domains.enrollment.repository import InMemoryEnrollmentRepository
from zbt.domains.identity.models import H5User
from zbt.domains.service_request.models import ServiceRequest
from zbt.domains.service_request.repository import InMemoryServiceRequestRepository
from zbt.domains.service_request.service import ServiceRequestService
from zbt.domains.service_request.vision import MaterialExtraction


class FakeVisionGateway:
    def recognize(
        self,
        *,
        content: bytes,
        media_type: str,
        material_type: str,
    ) -> MaterialExtraction:
        assert content == b"invoice-image"
        assert media_type == "image/jpeg"
        assert material_type == "MEDICAL_INVOICE"
        return MaterialExtraction(
            fields={
                "invoice_no": "INV-20260727-001",
                "hospital_name": "成都市第一人民医院",
                "visit_date": "2026-07-20",
                "amount_yuan": "1280.50",
            },
            confidence=0.94,
            warnings=("请人工核对票据号码",),
        )


def test_user_recognizes_confirms_and_submits_service_request() -> None:
    enrollments = InMemoryEnrollmentRepository()
    enrollments.save_policy(_policy())
    repository = InMemoryServiceRequestRepository()
    service = ServiceRequestService(
        repository,
        enrollments,
        FakeVisionGateway(),
        lambda: datetime(2026, 7, 27, 6, 0, tzinfo=UTC),
    )
    user = H5User(
        id="01USER00000000000000000001",
        mobile="13800138000",
        mobile_masked="138****8000",
        display_name="张先生",
        status="ACTIVE",
        created_at=datetime(2026, 7, 1, tzinfo=UTC),
    )

    recognition = service.recognize_material(
        user,
        policy_id="01POLICY00000000000000001",
        material_type="MEDICAL_INVOICE",
        source_name="医疗发票.jpg",
        media_type="image/jpeg",
        content=b"invoice-image",
    )
    request = service.submit_request(
        user,
        policy_id="01POLICY00000000000000001",
        request_type="MEDICAL_REIMBURSEMENT_ASSISTANCE",
        recognition_id=recognition["recognition_id"],
        confirmed_fields={
            **recognition["fields"],
            "contact_mobile": "13800138000",
        },
    )

    assert recognition["status"] == "PENDING_CONFIRMATION"
    assert "confidence" not in recognition
    assert request["request_no"].startswith("ZBT-SVC-20260727-")
    assert request["status"] == "SUBMITTED"
    assert service.list_user_requests(user)["total"] == 1
    assert "recognition" not in service.list_user_requests(user)["items"][0]

    admin_request = service.list_all_requests()["items"][0]
    assert admin_request["recognition"]["confidence"] == 0.94
    assert admin_request["recognition"]["material_available"] is True
    material, media_type, source_name = service.get_request_material(
        request["request_id"]
    )
    assert material.content == b"invoice-image"
    assert media_type == "image/jpeg"
    assert source_name == "医疗发票.jpg"

    reviewed = service.review_request(
        request["request_id"],
        decision="ACCEPTED",
        reviewer_id="01ADMIN000000000000000001",
    )

    assert reviewed["status"] == "ACCEPTED"
    assert reviewed["reviewed_by"] == "01ADMIN000000000000000001"
    assert reviewed["review_note"] is None
    assert service.list_user_requests(user)["items"][0]["status"] == "ACCEPTED"


def test_user_cannot_recognize_material_for_another_users_policy() -> None:
    enrollments = InMemoryEnrollmentRepository()
    enrollments.save_policy(_policy())
    service = ServiceRequestService(
        InMemoryServiceRequestRepository(),
        enrollments,
        FakeVisionGateway(),
        lambda: datetime(2026, 7, 27, 6, 0, tzinfo=UTC),
    )
    another_user = H5User(
        id="01OTHER0000000000000000001",
        mobile="13900139000",
        mobile_masked="139****9000",
        display_name="李女士",
        status="ACTIVE",
        created_at=datetime(2026, 7, 1, tzinfo=UTC),
    )

    with pytest.raises(AppError) as caught:
        service.recognize_material(
            another_user,
            policy_id="01POLICY00000000000000001",
            material_type="MEDICAL_INVOICE",
            source_name="医疗发票.jpg",
            media_type="image/jpeg",
            content=b"invoice-image",
        )

    assert caught.value.code == "POLICY_NOT_FOUND"


def test_rejected_request_uses_standard_reason() -> None:
    repository = InMemoryServiceRequestRepository()
    submitted_at = datetime(2026, 7, 27, 6, 0, tzinfo=UTC)
    repository.save_request(
        ServiceRequest(
            id="01SERVICE00000000000000001",
            request_no="ZBT-SVC-20260727-0001",
            user_id="01USER00000000000000000001",
            policy_id="01POLICY00000000000000001",
            request_type="MEDICAL_REIMBURSEMENT_ASSISTANCE",
            recognition_id="01RECOGNITION000000000001",
            confirmed_fields={},
            status="SUBMITTED",
            submitted_at=submitted_at,
        )
    )
    service = ServiceRequestService(
        repository,
        InMemoryEnrollmentRepository(),
        FakeVisionGateway(),
        lambda: submitted_at,
    )

    reviewed = service.review_request(
        "01SERVICE00000000000000001",
        decision="REJECTED",
        reviewer_id="01ADMIN000000000000000001",
    )

    assert reviewed["status"] == "REJECTED"
    assert reviewed["review_note"] == "不满足赔付条件"


def _policy() -> Policy:
    return Policy(
        id="01POLICY00000000000000001",
        policy_no="POL-20260727-0001",
        order_id="01ORDER000000000000000001",
        user_id="01USER00000000000000000001",
        product_id="01PRODUCT0000000000000001",
        product_version_id="01VERSION0000000000000001",
        plan_id="01PLAN0000000000000000001",
        premium_cents=29900,
        currency="CNY",
        coverage_start=date(2026, 7, 28),
        coverage_end=date(2027, 7, 27),
        status="ACTIVE",
        issued_at=datetime(2026, 7, 27, 5, 0, tzinfo=UTC),
    )
