from __future__ import annotations

import hashlib
import json
import uuid
from datetime import datetime
from decimal import Decimal
from typing import Any

from app.models.financial_record import ExpenseClaim
from app.services.expense_claim_risk_stage import enrich_risk_flag_semantics

PRE_REVIEW_PIPELINE_VERSION = "2026-07-16.1"
PRE_REVIEW_DECISIONS = {"ready", "needs_fix", "ready_with_review"}
_DERIVED_RISK_SOURCES = {
    "ai_pre_review",
    "application_submission",
    "approval",
    "approval_log",
    "approval_routing",
    "budget_approval",
    "expense_claim_approval",
    "expense_claim_finance_approval",
    "finance_approval",
    "manual_approval",
    "payment",
    "submission_review",
}


def build_pre_review_input_fingerprint(claim: ExpenseClaim) -> str:
    payload = {
        "claim": {
            "id": _text(claim.id),
            "employee_id": _text(claim.employee_id),
            "employee_name": _text(claim.employee_name),
            "department_id": _text(claim.department_id),
            "department_name": _text(claim.department_name),
            "project_code": _text(claim.project_code),
            "expense_type": _text(claim.expense_type),
            "reason": _text(claim.reason),
            "location": _text(claim.location),
            "amount": _money(claim.amount),
            "currency": _text(claim.currency),
            "occurred_at": _date_time(claim.occurred_at),
        },
        "items": [
            {
                "id": _text(item.id),
                "item_date": _date_time(item.item_date),
                "item_type": _text(item.item_type),
                "item_reason": _text(item.item_reason),
                "item_location": _text(item.item_location),
                "item_note": _text(item.item_note),
                "item_amount": _money(item.item_amount),
                "invoice_id": _text(item.invoice_id),
            }
            for item in sorted(list(claim.items or []), key=lambda entry: _text(entry.id))
        ],
        "risk_inputs": sorted(
            [
                _strip_volatile_fields(flag)
                for flag in list(claim.risk_flags_json or [])
                if isinstance(flag, dict)
                and _text(flag.get("source")).lower() not in _DERIVED_RISK_SOURCES
            ],
            key=_canonical_json,
        ),
    }
    return _fingerprint(payload)


def build_pre_review_rule_set_fingerprint(platform_rule_set_fingerprint: str) -> str:
    return _fingerprint(
        {
            "pipeline_version": PRE_REVIEW_PIPELINE_VERSION,
            "platform_rule_set_fingerprint": _text(platform_rule_set_fingerprint),
        }
    )


def build_pre_review_decision(
    claim: ExpenseClaim,
    *,
    risk_flags: list[Any],
    business_stage: str,
    platform_rule_set_fingerprint: str,
    reviewed_at: datetime,
) -> dict[str, Any]:
    input_fingerprint = build_pre_review_input_fingerprint(claim)
    rule_set_fingerprint = build_pre_review_rule_set_fingerprint(
        platform_rule_set_fingerprint
    )
    findings = _build_findings(risk_flags, business_stage=business_stage)
    review_context_fingerprint = _fingerprint(findings)
    blocking_findings = [
        finding
        for finding in findings
        if finding["severity"] in {"critical", "high"}
        and finding["disposition"] == "fix"
        and finding["resolution_status"] == "unresolved"
    ]
    if blocking_findings:
        decision = "needs_fix"
        message = (
            f"自动检测发现 {len(blocking_findings)} 条需先整改的重大风险，"
            "请按建议处理后重新预审。"
        )
    elif findings:
        decision = "ready_with_review"
        message = "自动检测已完成，当前风险可随单进入审批并由对应角色复核。"
    else:
        decision = "ready"
        message = "自动检测通过，费用明细和附件可提交审批。"

    review_id = str(
        uuid.uuid5(
            uuid.NAMESPACE_URL,
            ":".join(
                [
                    "expense-claim-pre-review",
                    _text(claim.id),
                    input_fingerprint,
                    rule_set_fingerprint,
                    review_context_fingerprint,
                ]
            ),
        )
    )
    return {
        "review_id": review_id,
        "input_fingerprint": input_fingerprint,
        "rule_set_fingerprint": rule_set_fingerprint,
        "review_context_fingerprint": review_context_fingerprint,
        "pipeline_version": PRE_REVIEW_PIPELINE_VERSION,
        "reviewed_at": reviewed_at.isoformat(),
        "decision": decision,
        "passed": decision != "needs_fix",
        "blocking_count": len(blocking_findings),
        "findings": findings,
        "message": message,
    }


def find_pre_review_flag(claim: ExpenseClaim) -> dict[str, Any] | None:
    for flag in reversed(list(claim.risk_flags_json or [])):
        if isinstance(flag, dict) and _text(flag.get("source")) == "ai_pre_review":
            return flag
    return None


def is_pre_review_current(
    claim: ExpenseClaim,
    flag: dict[str, Any] | None,
    *,
    platform_rule_set_fingerprint: str,
) -> bool:
    if not isinstance(flag, dict):
        return False
    return bool(
        _text(flag.get("review_id"))
        and _text(flag.get("input_fingerprint"))
        == build_pre_review_input_fingerprint(claim)
        and _text(flag.get("rule_set_fingerprint"))
        == build_pre_review_rule_set_fingerprint(platform_rule_set_fingerprint)
        and _text(flag.get("decision")) in PRE_REVIEW_DECISIONS
    )


def pre_review_identity_matches(
    flag: dict[str, Any] | None,
    *,
    review_id: str,
    input_fingerprint: str,
) -> bool:
    if not isinstance(flag, dict):
        return False
    normalized_review_id = _text(review_id)
    normalized_input_fingerprint = _text(input_fingerprint)
    if normalized_review_id and _text(flag.get("review_id")) != normalized_review_id:
        return False
    if (
        normalized_input_fingerprint
        and _text(flag.get("input_fingerprint")) != normalized_input_fingerprint
    ):
        return False
    return True


def pre_review_public_payload(flag: dict[str, Any] | None) -> dict[str, Any] | None:
    if not isinstance(flag, dict) or not _text(flag.get("review_id")):
        return None
    return {
        "review_id": _text(flag.get("review_id")),
        "input_fingerprint": _text(flag.get("input_fingerprint")),
        "rule_set_fingerprint": _text(flag.get("rule_set_fingerprint")),
        "review_context_fingerprint": _text(
            flag.get("review_context_fingerprint")
        ),
        "pipeline_version": _text(flag.get("pipeline_version")),
        "reviewed_at": _text(flag.get("reviewed_at") or flag.get("created_at")),
        "decision": _text(flag.get("decision")) or "ready_with_review",
        "passed": bool(flag.get("passed")),
        "blocking_count": int(
            flag.get("blocking_count") or flag.get("blocking_risk_count") or 0
        ),
        "message": _text(flag.get("message")),
        "findings": [
            dict(item)
            for item in list(flag.get("findings") or [])
            if isinstance(item, dict)
        ],
        "historical_case_evidence": _public_historical_evidence(flag),
    }


def _public_historical_evidence(flag: dict[str, Any]) -> list[dict[str, Any]]:
    result: list[dict[str, Any]] = []
    for item in list(flag.get("historical_case_evidence") or []):
        if not isinstance(item, dict):
            continue
        public_item = _historical_evidence_public_payload(item)
        if public_item is not None:
            result.append(public_item)
    return result


def _historical_evidence_public_payload(
    item: dict[str, Any],
) -> dict[str, Any] | None:
    label = _text(item.get("label")).lower()
    if label not in {"confirmed", "false_positive"}:
        return None
    return {
        "label": label,
        "label_text": (
            "历史已确认，仅供复核"
            if label == "confirmed"
            else "历史误报，仅供复核"
        ),
        "advisory_only": True,
        "score": round(float(item.get("score") or 0.0), 4),
        "scene_code": _text(item.get("scene_code")),
        "policy_ref": _text(item.get("policy_ref")),
        "rule_version": _text(item.get("rule_version")),
        "version_status": (
            "stale" if bool(item.get("stale")) else "matched"
        ),
        "stale": bool(item.get("stale")),
        "summary": (
            "历史相似案例经人工复核确认风险成立。"
            if label == "confirmed"
            else "历史相似案例经人工复核判定为误报。"
        ),
    }


def _build_findings(
    risk_flags: list[Any],
    *,
    business_stage: str,
) -> list[dict[str, Any]]:
    findings: list[dict[str, Any]] = []
    for flag in list(risk_flags or []):
        if not isinstance(flag, dict) or _text(flag.get("source")) == "ai_pre_review":
            continue
        enriched = enrich_risk_flag_semantics(flag, business_stage=business_stage)
        severity = _text(
            enriched.get("severity") or enriched.get("tone") or enriched.get("level")
        ).lower()
        if severity not in {"medium", "high", "critical"}:
            continue
        actionability = _text(enriched.get("actionability")).lower()
        if actionability == "system_trace":
            continue
        item_ids = _item_ids(enriched)
        resolution_status = _resolution_status(enriched)
        disposition = "fix" if actionability == "fixable_by_submitter" else "review"
        message = _text(
            enriched.get("message")
            or enriched.get("summary")
            or enriched.get("reason")
            or enriched.get("label")
        )
        risk_key = {
            "source": _text(enriched.get("source")),
            "rule_code": _text(enriched.get("rule_code")),
            "severity": severity,
            "item_ids": item_ids,
            "message": message,
        }
        findings.append(
            {
                "risk_id": _text(enriched.get("risk_id"))
                or f"risk:{_fingerprint(risk_key).removeprefix('sha256:')[:24]}",
                "rule_code": _text(enriched.get("rule_code")),
                "rule_version": _text(enriched.get("rule_version")),
                "severity": severity,
                "disposition": disposition,
                "resolution_status": resolution_status,
                "actionability": actionability,
                "source": _text(enriched.get("source")) or "pre_review_finding",
                "business_stage": _text(enriched.get("business_stage"))
                or business_stage,
                "risk_domain": _text(
                    enriched.get("risk_domain") or enriched.get("riskDomain")
                ),
                "visibility_scope": _text(
                    enriched.get("visibility_scope")
                    or enriched.get("visibilityScope")
                ),
                "item_ids": item_ids,
                "message": message,
                "remediation": _build_remediation(
                    disposition=disposition,
                    item_ids=item_ids,
                    flag=enriched,
                ),
            }
        )
    return sorted(
        findings,
        key=lambda finding: (
            {"critical": 0, "high": 1, "medium": 2}.get(finding["severity"], 9),
            finding["risk_id"],
        ),
    )[:50]


def _build_remediation(
    *,
    disposition: str,
    item_ids: list[str],
    flag: dict[str, Any],
) -> dict[str, Any]:
    if disposition != "fix":
        return {
            "action": "manual_review",
            "target_item_ids": item_ids,
            "required_fields": [],
        }
    corpus = " ".join(
        _text(flag.get(key))
        for key in ("label", "message", "summary", "rule_code")
    )
    remediation = {
        "action": "edit_item_note" if item_ids else "edit_claim",
        "target_item_ids": item_ids,
        "required_fields": ["item_note"] if item_ids else [],
    }
    if any(token in corpus for token in ("超标", "标准", "住宿", "金额")):
        remediation["alternative_action"] = "accept_standard_limit"
    return remediation


def _resolution_status(flag: dict[str, Any]) -> str:
    explicit = _text(flag.get("resolution_status") or flag.get("resolutionStatus")).lower()
    if explicit in {"resolved", "accepted", "waived"}:
        return "resolved"
    if explicit in {"unresolved", "open", "pending"}:
        return "unresolved"
    return "resolved" if bool(flag.get("resolved")) else "unresolved"


def _item_ids(flag: dict[str, Any]) -> list[str]:
    raw_values = [
        flag.get("item_id"),
        flag.get("itemId"),
        *_as_list(flag.get("item_ids")),
        *_as_list(flag.get("itemIds")),
    ]
    return sorted(
        dict.fromkeys(_text(value) for value in raw_values if _text(value))
    )


def _as_list(value: Any) -> list[Any]:
    if value is None:
        return []
    if isinstance(value, list):
        return value
    if isinstance(value, (tuple, set)):
        return list(value)
    return [value]


def _strip_volatile_fields(value: Any) -> Any:
    if isinstance(value, list):
        return sorted(
            [_strip_volatile_fields(item) for item in value],
            key=_canonical_json,
        )
    if not isinstance(value, dict):
        return value
    volatile_keys = {
        "created_at",
        "updated_at",
        "review_id",
        "input_fingerprint",
        "rule_set_fingerprint",
        "reviewed_at",
    }
    return {
        str(key): _strip_volatile_fields(item)
        for key, item in value.items()
        if str(key) not in volatile_keys
    }


def _fingerprint(value: Any) -> str:
    canonical = _canonical_json(value)
    return f"sha256:{hashlib.sha256(canonical.encode('utf-8')).hexdigest()}"


def _canonical_json(value: Any) -> str:
    return json.dumps(
        value,
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
        default=str,
    )


def _date_time(value: Any) -> str:
    if hasattr(value, "isoformat"):
        return str(value.isoformat())
    return _text(value)


def _money(value: Any) -> str:
    return f"{Decimal(value or Decimal('0.00')).quantize(Decimal('0.01')):.2f}"


def _text(value: Any) -> str:
    return str(value or "").strip()
