from __future__ import annotations

from datetime import UTC, datetime
from decimal import Decimal
from unittest.mock import MagicMock

from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy.pool import StaticPool

from app.db.base import Base
from app.models.few_shot_sample import FewShotSample
from app.models.financial_record import ExpenseClaim
from app.schemas.ontology import OntologyParseResult
from app.schemas.user_agent import UserAgentRequest
from app.services.expense_claim_historical_evidence import (
    ExpenseClaimHistoricalEvidenceService,
    build_user_agent_historical_evidence_notice,
)
from app.services.expense_claim_pre_review import ExpenseClaimPreReviewMixin
from app.services.expense_claim_pre_review_decision import pre_review_public_payload
from app.services.few_shot_retrieval import FewShotRetriever
from app.services.few_shot_store import FewShotStore
from app.services.user_agent_application import UserAgentApplicationMixin


def _session() -> Session:
    engine = create_engine(
        "sqlite+pysqlite:///:memory:",
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
    )
    Base.metadata.create_all(engine)
    return sessionmaker(bind=engine)()


def _claim(*, claim_id: str = "claim-history") -> ExpenseClaim:
    return ExpenseClaim(
        id=claim_id,
        claim_no=f"RE-{claim_id}",
        employee_id="employee-history",
        employee_name="张三",
        department_id="department-history",
        department_name="市场部",
        project_code="PRJ-HISTORY",
        expense_type="travel",
        reason="客户现场差旅",
        location="上海",
        amount=Decimal("888.00"),
        currency="CNY",
        invoice_count=1,
        occurred_at=datetime(2026, 7, 16, 9, 0, tzinfo=UTC),
        status="draft",
        approval_stage="待提交",
        risk_flags_json=[],
    )


def _finding() -> dict:
    return {
        "risk_id": "risk-history",
        "rule_code": "TRAVEL-001",
        "rule_version": "v2",
        "severity": "high",
        "disposition": "fix",
        "resolution_status": "unresolved",
        "actionability": "fixable_by_submitter",
        "source": "submission_review",
        "business_stage": "reimbursement",
        "risk_domain": "policy",
        "visibility_scope": "employee",
        "item_ids": [],
        "message": "住宿金额超过差旅标准。",
        "remediation": {
            "action": "补充说明",
            "target_item_ids": [],
            "required_fields": [],
            "alternative_action": None,
        },
    }


def test_runtime_retrieval_explicitly_passes_tenant_scene_and_rule_identity(
    monkeypatch,
) -> None:
    with _session() as db:
        retriever = MagicMock()
        retriever.retrieve_for_expense_case.return_value = [
            {
                "sample_id": "sample-confirmed",
                "label": "confirmed",
                "score": 0.96,
                "scene": "expense_reimbursement",
                "policy_ref": "TRAVEL-001",
                "rule_version": "v2",
                "stale": False,
                "conclusion": "该类超标案例经复核确认成立。",
            }
        ]
        monkeypatch.setattr(
            ExpenseClaimHistoricalEvidenceService,
            "_build_retriever",
            lambda _service: retriever,
        )

        evidence = ExpenseClaimHistoricalEvidenceService(db).retrieve(
            _claim(),
            tenant_id="tenant-a",
            business_stage="reimbursement",
            findings=[_finding()],
        )

        retriever.retrieve_for_expense_case.assert_called_once_with(
            tenant_id="tenant-a",
            scene="expense_reimbursement",
            policy_ref="TRAVEL-001",
            rule_version="v2",
            query="travel\n客户现场差旅\n上海\n住宿金额超过差旅标准。",
            top_k=3,
        )
        assert evidence[0]["label_text"] == "历史已确认，仅供复核"
        assert evidence[0]["advisory_only"] is True
        assert evidence[0]["summary"] == "历史相似案例经人工复核确认风险成立。"
        assert "sample_id" not in evidence[0]
        assert "conclusion" not in evidence[0]
        assert "该类超标案例经复核确认成立" not in repr(evidence[0])


def test_runtime_historical_evidence_db_recheck_never_exposes_other_tenant(
    monkeypatch,
) -> None:
    with _session() as db:
        db.add_all(
            [
                _sample(
                    sample_id="sample-a",
                    tenant_id="tenant-a",
                    label="confirmed",
                ),
                _sample(
                    sample_id="sample-b",
                    tenant_id="tenant-b",
                    label="false_positive",
                ),
            ]
        )
        db.commit()
        store = MagicMock(spec=FewShotStore)
        hits = [
            {"sample_id": "sample-b", "score": 0.99},
            {"sample_id": "sample-a", "score": 0.91},
        ]
        store.search.side_effect = [hits, hits]
        monkeypatch.setattr(
            ExpenseClaimHistoricalEvidenceService,
            "_build_retriever",
            lambda _service: FewShotRetriever(store, db),
        )

        evidence = ExpenseClaimHistoricalEvidenceService(db).retrieve(
            _claim(),
            tenant_id="tenant-a",
            business_stage="reimbursement",
            findings=[_finding()],
        )

        assert len(evidence) == 1
        assert evidence[0]["label"] == "confirmed"
        assert "sample_id" not in evidence[0]
        assert "conclusion" not in evidence[0]
        assert "tenant-a 的历史结论" not in repr(evidence)
        assert "tenant-b 的历史结论" not in repr(evidence)
        assert all(
            call.kwargs["tenant_id"] == "tenant-a"
            for call in store.search.call_args_list
        )


def test_retrieval_failure_returns_empty_and_hard_decision_is_unchanged(
    monkeypatch,
) -> None:
    with _session() as db:
        monkeypatch.setattr(
            ExpenseClaimHistoricalEvidenceService,
            "_build_retriever",
            MagicMock(side_effect=RuntimeError("qdrant unavailable")),
        )
        service = _PreReviewHarness(db)
        with_evidence = service._refresh_claim_pre_review_flags(
            _claim(claim_id="claim-with-evidence"),
            is_application_claim=False,
            reviewed_at=datetime(2026, 7, 16, 9, 0, tzinfo=UTC),
            tenant_id="tenant-a",
        )
        without_evidence = service._refresh_claim_pre_review_flags(
            _claim(claim_id="claim-with-evidence"),
            is_application_claim=False,
            reviewed_at=datetime(2026, 7, 16, 10, 0, tzinfo=UTC),
            tenant_id="",
        )

        assert with_evidence is not None
        assert without_evidence is not None
        for key in (
            "review_id",
            "input_fingerprint",
            "rule_set_fingerprint",
            "review_context_fingerprint",
            "decision",
            "passed",
            "blocking_count",
            "blocking_risk_count",
            "findings",
        ):
            assert with_evidence[key] == without_evidence[key]
        assert with_evidence["historical_case_evidence"] == []
        assert without_evidence["historical_case_evidence"] == []


def test_evidence_changes_only_advisory_payload_not_rule_decision(monkeypatch) -> None:
    with _session() as db:
        retriever = MagicMock()
        retriever.retrieve_for_expense_case.return_value = [
            {
                "sample_id": "sample-false-positive",
                "label": "false_positive",
                "score": 0.87,
                "scene": "expense_reimbursement",
                "policy_ref": "TRAVEL-001",
                "rule_version": "v1",
                "stale": True,
                "conclusion": "历史相似案例经人工复核为误报。",
            }
        ]
        monkeypatch.setattr(
            ExpenseClaimHistoricalEvidenceService,
            "_build_retriever",
            lambda _service: retriever,
        )
        service = _PreReviewHarness(db)
        with_evidence = service._refresh_claim_pre_review_flags(
            _claim(claim_id="same-claim"),
            is_application_claim=False,
            reviewed_at=datetime(2026, 7, 16, 9, 0, tzinfo=UTC),
            tenant_id="tenant-a",
        )
        without_evidence = service._refresh_claim_pre_review_flags(
            _claim(claim_id="same-claim"),
            is_application_claim=False,
            reviewed_at=datetime(2026, 7, 16, 10, 0, tzinfo=UTC),
            tenant_id="",
        )

        assert with_evidence is not None
        assert without_evidence is not None
        for key in (
            "review_id",
            "decision",
            "passed",
            "blocking_count",
            "blocking_risk_count",
            "findings",
        ):
            assert with_evidence[key] == without_evidence[key]
        assert with_evidence["historical_case_evidence"][0]["label_text"] == (
            "历史误报，仅供复核"
        )
        public_payload = pre_review_public_payload(with_evidence)
        assert public_payload is not None
        public_evidence = public_payload["historical_case_evidence"][0]
        assert public_evidence["advisory_only"] is True
        assert public_evidence["version_status"] == "stale"
        assert public_evidence["summary"] == "历史相似案例经人工复核判定为误报。"
        assert "sample_id" not in public_evidence
        assert "conclusion" not in public_evidence
        assert "历史相似案例经人工复核为误报" not in repr(public_evidence)
        assert "claim_no" not in repr(public_evidence)


def test_user_agent_displays_only_fixed_historical_labels() -> None:
    claim = _claim()
    claim.risk_flags_json = [
        {
            "source": "ai_pre_review",
            "historical_case_evidence": [
                {
                    "label": "confirmed",
                    "sample_id": "sensitive-sample-id",
                    "conclusion": "客户甲与员工乙的人工复核原文",
                    "claim_no": "RE-SENSITIVE",
                },
                {"label": "false_positive"},
            ],
        }
    ]
    service = UserAgentApplicationMixin()
    notice = build_user_agent_historical_evidence_notice(claim)
    answer = service._build_expense_application_answer(
        UserAgentRequest(
            run_id="history-labels",
            user_id="employee@example.com",
            message="提交申请",
            ontology=OntologyParseResult(run_id="history-labels"),
        ),
        facts={
            "application_no": "AP-20260716-001",
            "manager_name": "直属领导",
            "historical_case_evidence_notice": notice,
        },
        step="submitted",
    )

    assert "历史已确认，仅供复核" in answer
    assert "历史误报，仅供复核" in answer
    assert "sensitive-sample-id" not in answer
    assert "客户甲与员工乙" not in answer
    assert "RE-SENSITIVE" not in answer


def _sample(*, sample_id: str, tenant_id: str, label: str) -> FewShotSample:
    return FewShotSample(
        id=sample_id,
        tenant_id=tenant_id,
        sample_key=f"key-{sample_id}",
        scene="expense_reimbursement",
        policy_ref="TRAVEL-001",
        rule_version="v2",
        domain="expense",
        risk_type="travel_limit",
        risk_level="high",
        label=label,
        case_text="住宿金额超过差旅标准",
        conclusion_text=f"{tenant_id} 的历史结论",
        payload_json={},
        status="active",
    )


class _PreReviewHarness(ExpenseClaimPreReviewMixin):
    def __init__(self, db: Session) -> None:
        self.db = db

    @staticmethod
    def _run_ai_submission_review(_claim: ExpenseClaim) -> dict:
        return {
            "risk_flags": [
                {
                    "source": "submission_review",
                    "severity": "high",
                    "actionability": "fixable_by_submitter",
                    "rule_code": "TRAVEL-001",
                    "rule_version": "v2",
                    "message": "住宿金额超过差旅标准。",
                }
            ],
            "rule_set_fingerprint": "rules-v2",
        }
