from __future__ import annotations

from datetime import UTC, datetime, timedelta
from time import perf_counter
from typing import Any

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

from app.core.agent_enums import (
    AgentName,
    AgentPermissionLevel,
    AgentRunSource,
    AgentRunStatus,
    AgentToolType,
)
from app.models.agent_run import AgentRun
from app.schemas.finance_dashboard import FinanceDashboardRead
from app.services.agent_runs import AgentRunService
from app.services.finance_dashboard import FinanceDashboardService
from app.services.finance_dashboard_scope import (
    FINANCE_DASHBOARD_TASK_TYPE,
    normalize_finance_dashboard_tenant_id,
    resolve_finance_dashboard_data_scope,
)
from app.services.tenant_registry import DEFAULT_TENANT_ID

FINANCE_DASHBOARD_TOOL_NAME = "digital_employee.finance_dashboard.snapshot"
SNAPSHOT_TTL_SECONDS = 120
SNAPSHOT_SCHEMA_VERSION = "finance-dashboard-tenant-v3"


class FinanceDashboardSnapshotService:
    def __init__(self, db: Session, *, tenant_id: str = "default") -> None:
        self.db = db
        self.tenant_id = normalize_finance_dashboard_tenant_id(tenant_id)
        self.data_scope = resolve_finance_dashboard_data_scope(self.tenant_id)

    def build_dashboard(
        self,
        *,
        range_key: str = "近30日",
        start_date: Any = None,
        end_date: Any = None,
        trend_range: str = "近12天",
        department_range: str = "本月",
    ) -> FinanceDashboardRead:
        key = self._cache_key(
            tenant_id=self.tenant_id,
            data_scope=self.data_scope,
            range_key=range_key,
            start_date=start_date,
            end_date=end_date,
            trend_range=trend_range,
            department_range=department_range,
        )
        snapshot = self._latest_fresh_snapshot(key)
        if snapshot is not None:
            return snapshot
        return self.refresh_snapshot(
            range_key=range_key,
            start_date=start_date,
            end_date=end_date,
            trend_range=trend_range,
            department_range=department_range,
            source=AgentRunSource.SYSTEM_EVENT.value,
        )

    def refresh_default_snapshot(self) -> FinanceDashboardRead:
        if self.tenant_id != DEFAULT_TENANT_ID:
            raise ValueError("后台默认财务快照只能在 default 系统租户范围内生成。")
        return self.refresh_snapshot(
            range_key="近30日",
            trend_range="近12天",
            department_range="本月",
            source=AgentRunSource.SCHEDULE.value,
        )

    def refresh_snapshot(
        self,
        *,
        range_key: str,
        start_date: Any = None,
        end_date: Any = None,
        trend_range: str,
        department_range: str,
        source: str,
    ) -> FinanceDashboardRead:
        key = self._cache_key(
            tenant_id=self.tenant_id,
            data_scope=self.data_scope,
            range_key=range_key,
            start_date=start_date,
            end_date=end_date,
            trend_range=trend_range,
            department_range=department_range,
        )
        run_service = AgentRunService(self.db)
        run = run_service.create_run(
            agent=AgentName.HERMES.value,
            source=source,
            tenant_id=self.tenant_id,
            user_id="digital_employee",
            ontology_json={
                "scenario": "finance_dashboard",
                "intent": "snapshot",
                "tenant_id": self.tenant_id,
                "data_scope": self.data_scope,
            },
            route_json={
                "task_type": FINANCE_DASHBOARD_TASK_TYPE,
                "job_type": FINANCE_DASHBOARD_TASK_TYPE,
                "selected_agent": AgentName.HERMES.value,
                "snapshot_key": key,
                "tenant_id": self.tenant_id,
                "data_scope": self.data_scope,
                "params": {
                    "range_key": range_key,
                    "start_date": self._date_text(start_date),
                    "end_date": self._date_text(end_date),
                    "trend_range": trend_range,
                    "department_range": department_range,
                },
                "phase": "running",
                "heartbeat_at": datetime.now(UTC).isoformat(),
            },
            permission_level=AgentPermissionLevel.READ.value,
            status=AgentRunStatus.RUNNING.value,
        )
        timer = perf_counter()
        try:
            dashboard = FinanceDashboardService(
                self.db,
                tenant_id=self.tenant_id,
            ).build_dashboard(
                range_key=range_key,
                start_date=start_date,
                end_date=end_date,
                trend_range=trend_range,
                department_range=department_range,
            )
            duration_ms = int((perf_counter() - timer) * 1000)
            payload = dashboard.model_dump(mode="json")
            summary = self._summary(payload)
            run_service.record_tool_call(
                run_id=run.run_id,
                tool_type=AgentToolType.DATABASE.value,
                tool_name=FINANCE_DASHBOARD_TOOL_NAME,
                request_json={
                    "task_type": FINANCE_DASHBOARD_TASK_TYPE,
                    "snapshot_key": key,
                    "tenant_id": self.tenant_id,
                    "data_scope": self.data_scope,
                },
                response_json={
                    "task_type": FINANCE_DASHBOARD_TASK_TYPE,
                    "summary": summary,
                    "payload": payload,
                },
                status=AgentRunStatus.SUCCEEDED.value,
                duration_ms=duration_ms,
            )
            run_service.merge_route_json(
                run.run_id,
                {
                    "phase": "succeeded",
                    "snapshot_key": key,
                    "tenant_id": self.tenant_id,
                    "data_scope": self.data_scope,
                    "snapshot_payload": payload,
                    "summary": summary,
                    "expires_at": (
                        datetime.now(UTC) + timedelta(seconds=SNAPSHOT_TTL_SECONDS)
                    ).isoformat(),
                    "heartbeat_at": datetime.now(UTC).isoformat(),
                },
                status=AgentRunStatus.SUCCEEDED.value,
                result_summary=(
                    f"财务看板指标快照已生成：{summary['reimbursement_count']} 单，"
                    f"金额 {summary['reimbursement_amount']:.2f} 元。"
                ),
                finished_at=datetime.now(UTC),
            )
            return dashboard
        except Exception as exc:
            run_service.merge_route_json(
                run.run_id,
                {
                    "phase": "failed",
                    "snapshot_key": key,
                    "tenant_id": self.tenant_id,
                    "data_scope": self.data_scope,
                    "heartbeat_at": datetime.now(UTC).isoformat(),
                },
                status=AgentRunStatus.FAILED.value,
                error_message=str(exc),
                finished_at=datetime.now(UTC),
            )
            raise

    def _latest_fresh_snapshot(self, key: str) -> FinanceDashboardRead | None:
        now = datetime.now(UTC)
        for run in self._recent_snapshot_runs(
            tenant_id=self.tenant_id,
            data_scope=self.data_scope,
            snapshot_key=key,
        ):
            route_json = run.route_json or {}
            payload = route_json.get("snapshot_payload")
            if not isinstance(payload, dict):
                payload = self._payload_from_tool_call(run)
            if not isinstance(payload, dict):
                continue
            expires_at = self._parse_datetime(route_json.get("expires_at"))
            if expires_at is None or expires_at <= now:
                continue
            return FinanceDashboardRead.model_validate(payload)
        return None

    def _recent_snapshot_runs(
        self,
        *,
        tenant_id: str,
        data_scope: str,
        snapshot_key: str,
    ) -> list[AgentRun]:
        stmt = (
            select(AgentRun)
            .options(selectinload(AgentRun.tool_calls))
            .where(
                AgentRun.agent == AgentName.HERMES.value,
                AgentRun.status == AgentRunStatus.SUCCEEDED.value,
                AgentRun.route_json["task_type"].as_string() == FINANCE_DASHBOARD_TASK_TYPE,
                AgentRun.route_json["tenant_id"].as_string() == tenant_id,
                AgentRun.route_json["data_scope"].as_string() == data_scope,
                AgentRun.route_json["snapshot_key"].as_string() == snapshot_key,
            )
            .order_by(AgentRun.started_at.desc())
            .limit(80)
        )
        runs = list(self.db.scalars(stmt).all())
        return runs

    @staticmethod
    def _payload_from_tool_call(run: AgentRun) -> dict[str, Any] | None:
        for tool_call in run.tool_calls:
            if tool_call.tool_name != FINANCE_DASHBOARD_TOOL_NAME:
                continue
            payload = (tool_call.response_json or {}).get("payload")
            return payload if isinstance(payload, dict) else None
        return None

    @staticmethod
    def _summary(payload: dict[str, Any]) -> dict[str, Any]:
        totals = payload.get("totals") if isinstance(payload.get("totals"), dict) else {}
        return {
            "finance_snapshot_count": 1,
            "reimbursement_count": int(totals.get("reimbursementCount") or 0),
            "reimbursement_amount": float(totals.get("reimbursementAmount") or 0),
            "pending_payment_amount": float(totals.get("pendingPaymentAmount") or 0),
            "budget_metric_count": len(payload.get("budget_metrics") or []),
            "department_count": len(payload.get("department_ranking") or []),
        }

    @classmethod
    def _cache_key(
        cls,
        *,
        tenant_id: str,
        data_scope: str,
        range_key: str,
        start_date: Any,
        end_date: Any,
        trend_range: str,
        department_range: str,
    ) -> str:
        return "|".join(
            [
                cls._key_part(SNAPSHOT_SCHEMA_VERSION),
                cls._key_part(normalize_finance_dashboard_tenant_id(tenant_id)),
                cls._key_part(data_scope),
                cls._key_part(range_key),
                cls._key_part(cls._date_text(start_date)),
                cls._key_part(cls._date_text(end_date)),
                cls._key_part(trend_range),
                cls._key_part(department_range),
            ]
        )

    @staticmethod
    def _key_part(value: Any) -> str:
        text = str(value or "")
        return f"{len(text)}:{text}"

    @staticmethod
    def _date_text(value: Any) -> str:
        if value is None:
            return ""
        if hasattr(value, "isoformat"):
            return str(value.isoformat())
        return str(value or "")

    @staticmethod
    def _parse_datetime(value: Any) -> datetime | None:
        normalized = str(value or "").strip()
        if not normalized:
            return None
        try:
            parsed = datetime.fromisoformat(normalized)
        except ValueError:
            return None
        return parsed if parsed.tzinfo is not None else parsed.replace(tzinfo=UTC)
