from __future__ import annotations

from datetime import UTC, datetime

from sqlalchemy import or_, select
from sqlalchemy.orm import Session, selectinload

from app.algorithem.risk_graph import (
    RiskGraphClaimSnapshot,
    RiskGraphEvaluationContext,
    evaluate_financial_risk_graph,
)
from app.core.logging import get_logger
from app.models.financial_record import ExpenseClaim
from app.models.hermes_report import HermesRiskReport
from app.services.expense_claim_risk_stage import with_risk_business_stage
from app.services.finance_report_tenant import require_report_tenant_id
from app.services.risk_observations import RiskObservationService

logger = get_logger("app.services.hermes_risk_scanner")


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

    def scan_global_risks(
        self,
        log_id: str | None = None,
        run_id: str | None = None,
        tenant_id: str = "default",
    ) -> dict[str, int]:
        tenant = require_report_tenant_id(tenant_id)
        logger.info("Starting global risk scan for Hermes...")

        claims = self._fetch_unscanned_claims(tenant_id=tenant)
        if not claims:
            logger.info("No unscanned claims found. Aborting scan.")
            return {"scanned_claim_count": 0, "risk_observation_count": 0}

        logger.info(f"Fetched {len(claims)} claims to analyze.")
        observation_service = RiskObservationService(self.db)

        observation_count = 0
        scanned_claim_count = 0
        graph_node_count = 0
        graph_edge_count = 0
        now = datetime.now(UTC)
        grouped_claims = self._group_claims_by_tenant(claims)
        for tenant_id in sorted(grouped_claims):
            tenant_claims = sorted(grouped_claims[tenant_id], key=lambda item: str(item.id))
            snapshot_versions = {claim.id: claim.updated_at for claim in tenant_claims}
            result = evaluate_financial_risk_graph(
                RiskGraphEvaluationContext(
                    claims=[RiskGraphClaimSnapshot.from_orm(claim) for claim in tenant_claims],
                    target_claim_ids={claim.id for claim in tenant_claims},
                    history_stats=observation_service.build_history_stats(
                        tenant_id=tenant_id,
                        expense_types={str(claim.expense_type or "") for claim in tenant_claims},
                    ),
                )
            )
            graph_node_count += len(result.nodes)
            graph_edge_count += len(result.edges)
            observations_by_claim = {}
            for observation in result.observations:
                observations_by_claim.setdefault(observation.claim_id, []).append(observation)

            for snapshot_claim in tenant_claims:
                claim = observation_service.lock_claim_for_risk_write(
                    snapshot_claim.id,
                    tenant_id=tenant_id,
                    refresh=True,
                )
                if claim is None or not self._is_scan_eligible(claim):
                    continue
                if claim.updated_at != snapshot_versions.get(claim.id):
                    # 计算期间单据已变化，旧快照不再写回；下一轮重新扫描。
                    continue
                for observation in observations_by_claim.get(claim.id, []):
                    observation_service.upsert_observation(
                        observation,
                        tenant_id=tenant_id,
                        run_id=run_id,
                        execution_log_id=log_id,
                        claim_lock_acquired=True,
                    )
                    observation_count += 1
                    claim.hermes_risk_flag = True
                    claim.risk_flags_json = self._append_algorithm_flag(
                        claim,
                        observation.as_dict(),
                    )

                    if log_id:
                        self.db.add(
                            HermesRiskReport(
                                tenant_id=tenant_id,
                                claim_id=observation.claim_id,
                                execution_log_id=log_id,
                                risk_level=observation.risk_level,
                                risk_type=observation.risk_signal,
                                risk_description=observation.description,
                                related_claim_ids=[
                                    observation.claim_id,
                                    *observation.similar_case_claim_ids,
                                ],
                            )
                        )
                claim.hermes_scanned_at = now
                scanned_claim_count += 1

        self.db.commit()
        logger.info(
            "Hermes risk graph scan completed. Found %s observations.",
            observation_count,
        )
        return {
            "scanned_claim_count": scanned_claim_count,
            "risk_observation_count": observation_count,
            "graph_node_count": graph_node_count,
            "graph_edge_count": graph_edge_count,
        }

    @staticmethod
    def _group_claims_by_tenant(
        claims: list[ExpenseClaim],
    ) -> dict[str, list[ExpenseClaim]]:
        grouped: dict[str, list[ExpenseClaim]] = {}
        for claim in claims:
            tenant_id = require_report_tenant_id(claim.tenant_id)
            grouped.setdefault(tenant_id, []).append(claim)
        return grouped

    def _fetch_unscanned_claims(self, *, tenant_id: str) -> list[ExpenseClaim]:
        stmt = (
            select(ExpenseClaim)
            .options(selectinload(ExpenseClaim.items))
            .where(
                ExpenseClaim.tenant_id == tenant_id,
                ExpenseClaim.status.in_(["draft", "submitted", "review"]),
                or_(
                    ExpenseClaim.hermes_scanned_at.is_(None),
                    ExpenseClaim.hermes_risk_flag.is_(False),
                ),
            )
            .order_by(ExpenseClaim.id)
            .limit(50)
        )

        return list(self.db.scalars(stmt).all())

    @staticmethod
    def _is_scan_eligible(claim: ExpenseClaim) -> bool:
        return str(claim.status or "").strip().lower() in {"draft", "submitted", "review"}

    @staticmethod
    def _append_algorithm_flag(claim: ExpenseClaim, observation: dict) -> list:
        existing = list(claim.risk_flags_json or [])
        flag = with_risk_business_stage(
            {
                "source": "financial_risk_graph",
                "risk_signal": observation.get("risk_signal"),
                "severity": observation.get("risk_level"),
                "risk_score": observation.get("risk_score"),
                "confidence_score": observation.get("confidence_score"),
                "algorithm_version": observation.get("algorithm_version"),
                "observation_key": observation.get("observation_key"),
            },
            "reimbursement",
        )
        if any(
            isinstance(item, dict) and item.get("observation_key") == flag["observation_key"]
            for item in existing
        ):
            return existing
        return [*existing, flag]
