from __future__ import annotations

from datetime import UTC, datetime
from decimal import Decimal

from app.api.deps import CurrentUserContext
from app.models.employee import Employee
from app.models.financial_record import ExpenseClaim
from app.schemas.ontology import OntologyEntity, OntologyParseResult
from app.schemas.reimbursement import ExpenseClaimUpdate
from app.services.budget import BudgetService
from app.services.expense_cases import ExpenseCaseService
from app.services.expense_claims import ExpenseClaimService
from app.test_helpers.db import build_in_memory_session_factory


def _build_claim(
    *,
    claim_id: str,
    claim_no: str,
    tenant_id: str,
    employee: Employee,
) -> ExpenseClaim:
    return ExpenseClaim(
        id=claim_id,
        tenant_id=tenant_id,
        claim_no=claim_no,
        employee_id=employee.id,
        employee_name=employee.name,
        department_id=None,
        department_name="租户隔离部",
        project_code=None,
        expense_type="office",
        reason=f"{claim_no} 原始事由",
        location="上海",
        amount=Decimal("88.00"),
        currency="CNY",
        invoice_count=0,
        occurred_at=datetime(2026, 7, 16, tzinfo=UTC),
        submitted_at=None,
        status="draft",
        approval_stage="待提交",
        risk_flags_json=[],
    )


def _build_employee(*, tenant_id: str, suffix: str, name: str, email: str) -> Employee:
    return Employee(
        id=f"tenant-{suffix}-employee",
        tenant_id=tenant_id,
        employee_no=f"TENANT-{suffix.upper()}-001",
        name=name,
        email=email,
    )


def _current_user(tenant_id: str) -> CurrentUserContext:
    return CurrentUserContext(
        username="same-owner@example.com",
        name="同名员工",
        role_codes=["user"],
        is_admin=False,
        tenant_id=tenant_id,
    )


def test_same_identity_claims_are_isolated_by_server_tenant() -> None:
    session_factory = build_in_memory_session_factory()
    with session_factory() as db:
        employee_a = _build_employee(
            tenant_id="tenant-a",
            suffix="scope-a",
            name="同名员工",
            email="same-owner@example.com",
        )
        employee_b = _build_employee(
            tenant_id="tenant-b",
            suffix="scope-b",
            name="同名员工",
            email="same-owner@example.com",
        )
        default_employee = _build_employee(
            tenant_id="default",
            suffix="scope-default",
            name="同名员工",
            email="same-owner@example.com",
        )
        tenant_a_claim = _build_claim(
            claim_id="tenant-a-claim",
            claim_no="RE-TENANT-A",
            tenant_id="tenant-a",
            employee=employee_a,
        )
        tenant_b_claim = _build_claim(
            claim_id="tenant-b-claim",
            claim_no="RE-TENANT-B",
            tenant_id="tenant-b",
            employee=employee_b,
        )
        legacy_default_claim = _build_claim(
            claim_id="legacy-default-claim",
            claim_no="RE-LEGACY-DEFAULT",
            tenant_id="default",
            employee=default_employee,
        )
        db.add_all(
            [
                employee_a,
                employee_b,
                default_employee,
                tenant_a_claim,
                tenant_b_claim,
                legacy_default_claim,
            ]
        )
        db.flush()
        case_service = ExpenseCaseService(db)
        case_service.ensure_case_for_claim(tenant_a_claim, tenant_id="tenant-a")
        case_service.ensure_case_for_claim(tenant_b_claim, tenant_id="tenant-b")
        db.commit()

        service = ExpenseClaimService(db)
        tenant_a_user = _current_user("tenant-a")
        tenant_b_user = _current_user("tenant-b")
        default_user = _current_user("default")

        assert {claim.id for claim in service.list_claims(tenant_a_user)} == {tenant_a_claim.id}
        assert {claim.id for claim in service.list_claims(tenant_b_user)} == {tenant_b_claim.id}
        assert {claim.id for claim in service.list_claims(default_user)} == {
            legacy_default_claim.id
        }
        assert service.get_claim(tenant_b_claim.id, tenant_a_user) is None
        assert service.get_claim(tenant_a_claim.id, tenant_b_user) is None

        assert (
            service.update_claim(
                claim_id=tenant_b_claim.id,
                payload=ExpenseClaimUpdate(reason="跨租户篡改"),
                current_user=tenant_a_user,
            )
            is None
        )
        db.expire_all()
        assert db.get(ExpenseClaim, tenant_b_claim.id).reason == "RE-TENANT-B 原始事由"


def _seed_cross_tenant_risk_history(db):
    employee_a = _build_employee(
        tenant_id="tenant-a",
        suffix="history-a",
        name="同名风险员工",
        email="same-risk-owner@example.com",
    )
    employee_b = _build_employee(
        tenant_id="tenant-b",
        suffix="history-b",
        name="同名风险员工",
        email="same-risk-owner@example.com",
    )
    clean_claim = _build_claim(
        claim_id="tenant-a-clean-claim",
        claim_no="RE-TENANT-A-CLEAN",
        tenant_id="tenant-a",
        employee=employee_a,
    )
    risky_claim = _build_claim(
        claim_id="tenant-b-risky-claim",
        claim_no="RE-TENANT-B-RISKY",
        tenant_id="tenant-b",
        employee=employee_b,
    )
    risky_claim.risk_flags_json = [
        {
            "source": "submission_review",
            "severity": "high",
            "label": "跨租户风险",
            "message": "该风险只属于 tenant-b。",
        }
    ]
    db.add_all([employee_a, employee_b, clean_claim, risky_claim])
    db.flush()
    case_service = ExpenseCaseService(db)
    case_service.ensure_case_for_claim(clean_claim, tenant_id="tenant-a")
    case_service.ensure_case_for_claim(risky_claim, tenant_id="tenant-b")
    db.commit()
    return clean_claim


def test_cross_tenant_risk_history_does_not_pollute_pre_review(monkeypatch) -> None:
    with build_in_memory_session_factory()() as db:
        clean_claim = _seed_cross_tenant_risk_history(db)
        service = ExpenseClaimService(db)
        monkeypatch.setattr(service, "_resolve_claim_manager_name", lambda _claim: "直属领导")
        monkeypatch.setattr(
            service,
            "_run_travel_policy_review",
            lambda _claim: {"flags": [], "blocking_reasons": []},
        )
        monkeypatch.setattr(
            service,
            "_run_scene_policy_review",
            lambda _claim: {"flags": [], "blocking_reasons": []},
        )
        monkeypatch.setattr(
            service,
            "evaluate_platform_risk_rules",
            lambda _claim, **_kwargs: {"flags": [], "rule_set_fingerprint": ""},
        )

        assert service._count_recent_risky_claims(clean_claim) == 0
        pre_review = service.refresh_claim_pre_review_state(
            clean_claim,
            is_application_claim=False,
        )

        assert pre_review is not None
        assert pre_review["decision"] == "ready"
        assert not any(
            isinstance(flag, dict) and str(flag.get("label") or "").startswith("历史风险")
            for flag in clean_claim.risk_flags_json
        )


def test_cross_tenant_risk_history_does_not_route_to_p8(monkeypatch) -> None:
    with build_in_memory_session_factory()() as db:
        clean_claim = _seed_cross_tenant_risk_history(db)
        monkeypatch.setattr(
            BudgetService,
            "analyze_claim_budget",
            lambda _self, _claim: {
                "score": 100,
                "rating": "pass",
                "risk_level": "low",
                "summary": "预算正常",
                "metrics": {},
                "budget_context": {"budget_applicable": False},
            },
        )
        service = ExpenseClaimService(db)

        assert service._count_recent_substantive_risky_claims(clean_claim) == 0
        route = service._build_approval_route_decision(
            clean_claim,
            is_application_claim=False,
        )

        assert route["historical_risk_count"] == 0
        assert route["requires_budget_review"] is False
        assert route["route"] == "finance"


def test_draft_lookup_never_returns_cross_tenant_claim_and_keeps_default_legacy() -> None:
    session_factory = build_in_memory_session_factory()
    with session_factory() as db:
        employee_a = _build_employee(
            tenant_id="tenant-a",
            suffix="draft-a",
            name="同名草稿员工",
            email="same-draft-owner@example.com",
        )
        employee_b = _build_employee(
            tenant_id="tenant-b",
            suffix="draft-b",
            name="同名草稿员工",
            email="same-draft-owner@example.com",
        )
        default_employee = _build_employee(
            tenant_id="default",
            suffix="draft-default",
            name="同名草稿员工",
            email="same-draft-owner@example.com",
        )
        tenant_a_claim = _build_claim(
            claim_id="tenant-a-draft",
            claim_no="RE-TENANT-A-DRAFT",
            tenant_id="tenant-a",
            employee=employee_a,
        )
        tenant_b_claim = _build_claim(
            claim_id="tenant-b-draft",
            claim_no="RE-TENANT-B-DRAFT",
            tenant_id="tenant-b",
            employee=employee_b,
        )
        legacy_default_claim = _build_claim(
            claim_id="legacy-default-draft",
            claim_no="RE-LEGACY-DEFAULT-DRAFT",
            tenant_id="default",
            employee=default_employee,
        )
        db.add_all(
            [
                employee_a,
                employee_b,
                default_employee,
                tenant_a_claim,
                tenant_b_claim,
                legacy_default_claim,
            ]
        )
        db.flush()
        case_service = ExpenseCaseService(db)
        case_service.ensure_case_for_claim(tenant_a_claim, tenant_id="tenant-a")
        case_service.ensure_case_for_claim(tenant_b_claim, tenant_id="tenant-b")
        db.commit()

        service = ExpenseClaimService(db)
        tenant_b_ontology = OntologyParseResult(
            run_id="tenant-b-draft-lookup",
            entities=[
                OntologyEntity(
                    type="expense_claim",
                    value=tenant_b_claim.claim_no,
                    normalized_value=tenant_b_claim.claim_no,
                    confidence=1.0,
                )
            ],
        )
        empty_ontology = OntologyParseResult(run_id="tenant-draft-candidate")

        assert (
            service._find_target_claim(
                ontology=empty_ontology,
                context_json={
                    "tenant_id": "tenant-a",
                    "draft_claim_id": tenant_b_claim.id,
                },
            )
            is None
        )
        assert (
            service._find_target_claim(
                ontology=tenant_b_ontology,
                context_json={"tenantId": "tenant-a"},
            )
            is None
        )
        association_candidate = service._find_association_candidate(
            ontology=empty_ontology,
            context_json={
                "tenant_id": "tenant-a",
                "draft_claim_id": tenant_b_claim.id,
            },
            user_id=employee_a.email,
            employee=employee_a,
        )
        assert association_candidate is not None
        assert association_candidate.id == tenant_a_claim.id

        legacy_by_id = service._find_target_claim(
            ontology=empty_ontology,
            context_json={
                "tenant_id": "default",
                "draft_claim_id": legacy_default_claim.id,
            },
        )
        assert legacy_by_id is not None
        assert legacy_by_id.id == legacy_default_claim.id
