from __future__ import annotations

from datetime import UTC, date, datetime
from decimal import Decimal

from app.models.financial_record import ExpenseClaim, ExpenseClaimItem
from app.services.expense_claim_pre_review_decision import (
    build_pre_review_decision,
    pre_review_identity_matches,
)


def build_claim(*, risk_flags: list[dict] | None = None) -> ExpenseClaim:
    claim = ExpenseClaim(
        id="claim-pre-review-decision",
        claim_no="RE-PRE-REVIEW-DECISION",
        employee_id="employee-pre-review",
        employee_name="张三",
        department_id="department-pre-review",
        department_name="市场部",
        project_code="PRJ-PRE-REVIEW",
        expense_type="travel",
        reason="客户现场差旅",
        location="上海",
        amount=Decimal("88.00"),
        currency="CNY",
        invoice_count=1,
        occurred_at=datetime(2026, 7, 16, 9, 0, tzinfo=UTC),
        status="draft",
        approval_stage="待提交",
        risk_flags_json=list(risk_flags or []),
    )
    claim.items = [
        ExpenseClaimItem(
            id="item-pre-review-decision",
            claim_id=claim.id,
            item_date=date(2026, 7, 16),
            item_type="travel",
            item_reason="客户现场交通",
            item_location="上海",
            item_note="",
            item_amount=Decimal("88.00"),
            invoice_id="receipt.png",
        )
    ]
    return claim


def test_pre_review_identity_is_stable_when_risk_input_order_changes() -> None:
    risk_flags = [
        {"source": "manual_a", "severity": "low", "message": "A"},
        {"source": "manual_b", "severity": "medium", "message": "B"},
    ]
    first = build_pre_review_decision(
        build_claim(risk_flags=risk_flags),
        risk_flags=risk_flags,
        business_stage="reimbursement",
        platform_rule_set_fingerprint="rules-v1",
        reviewed_at=datetime(2026, 7, 16, 9, 0, tzinfo=UTC),
    )
    repeated = build_pre_review_decision(
        build_claim(risk_flags=list(reversed(risk_flags))),
        risk_flags=list(reversed(risk_flags)),
        business_stage="reimbursement",
        platform_rule_set_fingerprint="rules-v1",
        reviewed_at=datetime(2026, 7, 16, 10, 0, tzinfo=UTC),
    )

    assert repeated["review_id"] == first["review_id"]
    assert repeated["input_fingerprint"] == first["input_fingerprint"]


def test_pre_review_identity_is_stable_when_nested_risk_ids_change_order() -> None:
    first_flags = [
        {
            "source": "manual_risk",
            "severity": "medium",
            "message": "多条明细需复核。",
            "item_ids": ["item-b", "item-a"],
            "basic_rule_refs": ["rule-b", "rule-a"],
        }
    ]
    repeated_flags = [
        {
            **first_flags[0],
            "item_ids": ["item-a", "item-b"],
            "basic_rule_refs": ["rule-a", "rule-b"],
        }
    ]

    first = build_pre_review_decision(
        build_claim(risk_flags=first_flags),
        risk_flags=first_flags,
        business_stage="reimbursement",
        platform_rule_set_fingerprint="rules-v1",
        reviewed_at=datetime(2026, 7, 16, 9, 0, tzinfo=UTC),
    )
    repeated = build_pre_review_decision(
        build_claim(risk_flags=repeated_flags),
        risk_flags=repeated_flags,
        business_stage="reimbursement",
        platform_rule_set_fingerprint="rules-v1",
        reviewed_at=datetime(2026, 7, 16, 10, 0, tzinfo=UTC),
    )

    assert repeated["review_id"] == first["review_id"]
    assert repeated["review_context_fingerprint"] == first[
        "review_context_fingerprint"
    ]


def test_pre_review_identity_changes_when_dynamic_findings_change() -> None:
    claim = build_claim()
    ready = build_pre_review_decision(
        claim,
        risk_flags=[],
        business_stage="reimbursement",
        platform_rule_set_fingerprint="rules-v1",
        reviewed_at=datetime(2026, 7, 16, 9, 0, tzinfo=UTC),
    )
    changed = build_pre_review_decision(
        claim,
        risk_flags=[
            {
                "source": "submission_review",
                "severity": "high",
                "actionability": "fixable_by_submitter",
                "message": "预审后发现同一发票已被其他单据使用。",
            }
        ],
        business_stage="reimbursement",
        platform_rule_set_fingerprint="rules-v1",
        reviewed_at=datetime(2026, 7, 16, 10, 0, tzinfo=UTC),
    )

    assert changed["input_fingerprint"] == ready["input_fingerprint"]
    assert changed["review_context_fingerprint"] != ready[
        "review_context_fingerprint"
    ]
    assert changed["review_id"] != ready["review_id"]
    assert changed["decision"] == "needs_fix"


def test_pre_review_blocks_high_fixable_risk_and_normalizes_string_item_id() -> None:
    claim = build_claim()
    decision = build_pre_review_decision(
        claim,
        risk_flags=[
            {
                "source": "manual_risk",
                "severity": "high",
                "actionability": "fixable_by_submitter",
                "message": "住宿金额超过标准，请修正。",
                "item_ids": "item-pre-review-decision",
            }
        ],
        business_stage="reimbursement",
        platform_rule_set_fingerprint="rules-v1",
        reviewed_at=datetime(2026, 7, 16, 9, 0, tzinfo=UTC),
    )

    assert decision["decision"] == "needs_fix"
    assert decision["blocking_count"] == 1
    assert decision["findings"][0]["item_ids"] == ["item-pre-review-decision"]
    assert (
        decision["findings"][0]["remediation"]["alternative_action"]
        == "accept_standard_limit"
    )


def test_pre_review_routes_budget_governance_risk_to_approval_instead_of_blocking() -> None:
    claim = build_claim()
    decision = build_pre_review_decision(
        claim,
        risk_flags=[
            {
                "source": "budget_control",
                "severity": "critical",
                "risk_domain": "budget",
                "actionability": "budget_governance",
                "message": "预算使用率超过治理阈值。",
            }
        ],
        business_stage="reimbursement",
        platform_rule_set_fingerprint="rules-v1",
        reviewed_at=datetime(2026, 7, 16, 9, 0, tzinfo=UTC),
    )

    assert decision["decision"] == "ready_with_review"
    assert decision["blocking_count"] == 0
    assert decision["findings"][0]["disposition"] == "review"
    assert pre_review_identity_matches(
        {**decision},
        review_id=decision["review_id"],
        input_fingerprint=decision["input_fingerprint"],
    )
