"""Data contracts for the financial risk graph algorithm."""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import date, datetime
from decimal import Decimal
from typing import Any

ALGORITHM_VERSION = "financial_risk_graph.v1"

LEVEL_LOW = "low"
LEVEL_MEDIUM = "medium"
LEVEL_HIGH = "high"
LEVEL_CRITICAL = "critical"

AUTOMATION_ASSIST = "assist"
AUTOMATION_MANUAL_REVIEW = "manual_review"
AUTOMATION_SEMI_AUTO_REVIEW = "semi_auto_review"
AUTOMATION_AUTO_HOLD = "auto_hold"


@dataclass(slots=True)
class RiskGraphClaimItemSnapshot:
    item_id: str = ""
    item_type: str = ""
    item_amount: Any = Decimal("0")
    item_location: str = ""
    item_date: date | None = None
    invoice_id: str | None = None
    metadata: dict[str, Any] = field(default_factory=dict)

    @classmethod
    def from_orm(cls, item: Any) -> "RiskGraphClaimItemSnapshot":
        return cls(
            item_id=str(getattr(item, "id", "") or ""),
            item_type=str(getattr(item, "item_type", "") or ""),
            item_amount=getattr(item, "item_amount", Decimal("0")) or Decimal("0"),
            item_location=str(getattr(item, "item_location", "") or ""),
            item_date=getattr(item, "item_date", None),
            invoice_id=(
                str(getattr(item, "invoice_id", "") or "").strip()
                or None
            ),
            metadata=_metadata_from_object(item),
        )


@dataclass(slots=True)
class RiskGraphClaimSnapshot:
    claim_id: str
    claim_no: str = ""
    employee_id: str | None = None
    employee_name: str = ""
    department_id: str | None = None
    department_name: str = ""
    employee_grade: str | None = None
    expense_type: str = ""
    amount: Any = Decimal("0")
    currency: str = "CNY"
    invoice_count: int = 0
    occurred_at: datetime | None = None
    submitted_at: datetime | None = None
    status: str = ""
    reason: str = ""
    location: str = ""
    risk_flags: list[Any] = field(default_factory=list)
    items: list[RiskGraphClaimItemSnapshot] = field(default_factory=list)
    metadata: dict[str, Any] = field(default_factory=dict)

    @classmethod
    def from_orm(cls, claim: Any) -> "RiskGraphClaimSnapshot":
        items = [
            RiskGraphClaimItemSnapshot.from_orm(item)
            for item in list(getattr(claim, "items", None) or [])
        ]
        return cls(
            claim_id=str(getattr(claim, "id", "") or ""),
            claim_no=str(getattr(claim, "claim_no", "") or ""),
            employee_id=(
                str(getattr(claim, "employee_id", "") or "").strip()
                or None
            ),
            employee_name=str(getattr(claim, "employee_name", "") or ""),
            department_id=(
                str(getattr(claim, "department_id", "") or "").strip()
                or None
            ),
            department_name=str(getattr(claim, "department_name", "") or ""),
            employee_grade=(
                str(getattr(claim, "employee_grade", "") or "").strip()
                or None
            ),
            expense_type=str(getattr(claim, "expense_type", "") or ""),
            amount=getattr(claim, "amount", Decimal("0")) or Decimal("0"),
            currency=str(getattr(claim, "currency", "CNY") or "CNY"),
            invoice_count=int(getattr(claim, "invoice_count", 0) or 0),
            occurred_at=getattr(claim, "occurred_at", None),
            submitted_at=getattr(claim, "submitted_at", None),
            status=str(getattr(claim, "status", "") or ""),
            reason=str(getattr(claim, "reason", "") or ""),
            location=str(getattr(claim, "location", "") or ""),
            risk_flags=list(getattr(claim, "risk_flags_json", None) or []),
            items=items,
            metadata=_metadata_from_object(claim),
        )


@dataclass(slots=True)
class RiskGraphNode:
    key: str
    node_type: str
    label: str
    canonical_key: str = ""
    canonical_id: str = ""
    ontology_type: str = ""
    ontology_parse_id: str = ""
    ontology_version: str = ""
    metadata: dict[str, Any] = field(default_factory=dict)

    def as_dict(self) -> dict[str, Any]:
        return {
            "key": self.key,
            "node_type": self.node_type,
            "label": self.label,
            "canonical_key": self.canonical_key or self.key,
            "canonical_id": self.canonical_id or self.canonical_key or self.key,
            "ontology_type": self.ontology_type or self.node_type,
            "ontology_parse_id": self.ontology_parse_id,
            "ontology_version": self.ontology_version,
            "metadata": _json_safe(self.metadata),
        }


@dataclass(slots=True)
class RiskGraphEdge:
    source_key: str
    target_key: str
    edge_type: str
    weight: Decimal = Decimal("1")
    source: str = "algorithm"
    evidence: str = ""
    metadata: dict[str, Any] = field(default_factory=dict)

    def edge_key(self) -> tuple[str, str, str]:
        return (self.source_key, self.target_key, self.edge_type)

    def as_dict(self) -> dict[str, Any]:
        return {
            "source_key": self.source_key,
            "target_key": self.target_key,
            "edge_type": self.edge_type,
            "weight": _format_decimal(self.weight),
            "source": self.source,
            "evidence": self.evidence,
            "metadata": _json_safe(self.metadata),
        }


@dataclass(slots=True)
class PeerBaseline:
    scope: str
    sample_size: int
    median_amount: Decimal = Decimal("0")
    p75_amount: Decimal = Decimal("0")
    p90_amount: Decimal = Decimal("0")
    mean_amount: Decimal = Decimal("0")
    fallback_reason: str = ""

    def as_dict(self) -> dict[str, Any]:
        return {
            "scope": self.scope,
            "sample_size": self.sample_size,
            "median_amount": _format_decimal(self.median_amount),
            "p75_amount": _format_decimal(self.p75_amount),
            "p90_amount": _format_decimal(self.p90_amount),
            "mean_amount": _format_decimal(self.mean_amount),
            "fallback_reason": self.fallback_reason,
        }


@dataclass(slots=True)
class RiskEvidence:
    code: str
    title: str
    detail: str
    source: str
    score: int = 0
    related_entity_keys: list[str] = field(default_factory=list)
    metadata: dict[str, Any] = field(default_factory=dict)

    def as_dict(self) -> dict[str, Any]:
        return {
            "code": self.code,
            "title": self.title,
            "detail": self.detail,
            "source": self.source,
            "score": int(self.score),
            "related_entity_keys": list(self.related_entity_keys),
            "metadata": _json_safe(self.metadata),
        }


@dataclass(slots=True)
class RiskHistoryStats:
    risk_signal: str
    expense_type: str = ""
    similar_case_count: int = 0
    confirmed_count: int = 0
    false_positive_count: int = 0
    returned_count: int = 0

    def as_dict(self) -> dict[str, Any]:
        return {
            "risk_signal": self.risk_signal,
            "expense_type": self.expense_type,
            "similar_case_count": self.similar_case_count,
            "confirmed_count": self.confirmed_count,
            "false_positive_count": self.false_positive_count,
            "returned_count": self.returned_count,
        }


@dataclass(slots=True)
class RiskGraphEvaluationContext:
    claims: list[RiskGraphClaimSnapshot]
    target_claim_ids: set[str] | None = None
    ontology_parse: Any | None = None
    ontology_parse_id: str = ""
    ontology_version: str = "ontology.v1"
    history_stats: list[RiskHistoryStats] = field(default_factory=list)
    min_peer_sample_size: int = 3
    observation_threshold: int = 31
    near_threshold_amount: Decimal = Decimal("5000")


@dataclass(slots=True)
class RiskObservationDraft:
    observation_key: str
    subject_type: str
    subject_key: str
    subject_label: str
    claim_id: str
    claim_no: str
    risk_type: str
    risk_signal: str
    title: str
    description: str
    risk_score: int
    risk_level: str
    confidence_score: Decimal
    control_stage: str
    control_mode: str
    automation_mode: str
    source: str
    algorithm_version: str
    contribution_scores: dict[str, int]
    baseline: PeerBaseline
    evidence: list[RiskEvidence] = field(default_factory=list)
    graph_node_keys: list[str] = field(default_factory=list)
    graph_edge_keys: list[dict[str, str]] = field(default_factory=list)
    policy_refs: list[str] = field(default_factory=list)
    similar_case_claim_ids: list[str] = field(default_factory=list)
    ontology_json: dict[str, Any] = field(default_factory=dict)
    decision_trace: dict[str, Any] = field(default_factory=dict)

    def as_dict(self) -> dict[str, Any]:
        return {
            "observation_key": self.observation_key,
            "subject_type": self.subject_type,
            "subject_key": self.subject_key,
            "subject_label": self.subject_label,
            "claim_id": self.claim_id,
            "claim_no": self.claim_no,
            "risk_type": self.risk_type,
            "risk_signal": self.risk_signal,
            "title": self.title,
            "description": self.description,
            "risk_score": self.risk_score,
            "risk_level": self.risk_level,
            "confidence_score": _format_decimal(self.confidence_score),
            "control_stage": self.control_stage,
            "control_mode": self.control_mode,
            "automation_mode": self.automation_mode,
            "source": self.source,
            "algorithm_version": self.algorithm_version,
            "contribution_scores": dict(self.contribution_scores),
            "baseline": self.baseline.as_dict(),
            "evidence": [item.as_dict() for item in self.evidence],
            "graph_node_keys": list(self.graph_node_keys),
            "graph_edge_keys": list(self.graph_edge_keys),
            "policy_refs": list(self.policy_refs),
            "similar_case_claim_ids": list(self.similar_case_claim_ids),
            "ontology_json": _json_safe(self.ontology_json),
            "decision_trace": _json_safe(self.decision_trace),
        }


@dataclass(slots=True)
class RiskGraphEvaluationResult:
    observations: list[RiskObservationDraft]
    nodes: list[RiskGraphNode]
    edges: list[RiskGraphEdge]
    algorithm_version: str = ALGORITHM_VERSION

    def as_dict(self) -> dict[str, Any]:
        return {
            "algorithm_version": self.algorithm_version,
            "observations": [item.as_dict() for item in self.observations],
            "nodes": [item.as_dict() for item in self.nodes],
            "edges": [item.as_dict() for item in self.edges],
            "summary": {
                "observation_count": len(self.observations),
                "node_count": len(self.nodes),
                "edge_count": len(self.edges),
                "high_or_above_count": sum(
                    1
                    for item in self.observations
                    if item.risk_level in {LEVEL_HIGH, LEVEL_CRITICAL}
                ),
            },
        }


def _format_decimal(value: Any, places: str = "0.0000") -> str:
    if value is None:
        return "0"
    if not isinstance(value, Decimal):
        value = Decimal(str(value or "0"))
    return format(value.quantize(Decimal(places)), "f").rstrip("0").rstrip(".") or "0"


def _json_safe(value: Any) -> Any:
    if isinstance(value, Decimal):
        return _format_decimal(value)
    if isinstance(value, (datetime, date)):
        return value.isoformat()
    if isinstance(value, list):
        return [_json_safe(item) for item in value]
    if isinstance(value, tuple):
        return [_json_safe(item) for item in value]
    if isinstance(value, dict):
        return {str(key): _json_safe(item) for key, item in value.items()}
    return value


def _metadata_from_object(source: Any) -> dict[str, Any]:
    metadata: dict[str, Any] = {}
    for attr in (
        "metadata",
        "metadata_json",
        "extra_json",
        "supplier_id",
        "supplier_name",
        "vendor_id",
        "vendor_name",
        "merchant_id",
        "merchant_name",
    ):
        value = getattr(source, attr, None)
        if isinstance(value, dict):
            metadata.update(value)
        elif attr != "metadata" and value not in (None, ""):
            metadata[attr] = value
    return metadata
