from __future__ import annotations

from typing import Any

from app.schemas.steward import (
    StewardCandidateFlow,
    StewardFlowStatePatch,
    StewardPendingFlowConfirmation,
    StewardPlanResponse,
    StewardTask,
)
from app.services.ontology_field_registry import (
    CANONICAL_ONTOLOGY_FIELDS,
    normalize_ontology_form_values,
)


class StewardFlowStateService:
    """维护小财管家跨轮对话的本体业务状态。"""

    EVENT_LIMIT = 80

    def merge_state(
        self,
        current_state: dict[str, Any] | None,
        patch: StewardFlowStatePatch,
    ) -> dict[str, Any]:
        state = self._normalize_state(current_state)
        flow = dict(state["flows"].get(patch.flow_id) or {})
        existing_fields = dict(flow.get("fields") or {})
        next_fields = {
            **existing_fields,
            **self._normalize_fields(patch.fields),
        }

        flow.update(
            {
                "flow_id": patch.flow_id,
                "status": str(patch.status or "collecting").strip() or "collecting",
                "intent": str(patch.intent or flow.get("intent") or "").strip(),
                "fields": next_fields,
                "missing_fields": self._normalize_missing_fields(patch.missing_fields),
            }
        )
        if patch.application_claim_id:
            flow["application_claim_id"] = patch.application_claim_id
        if patch.linked_application_claim_id:
            flow["linked_application_claim_id"] = patch.linked_application_claim_id
        if patch.attachments:
            flow["attachments"] = self._merge_attachments(flow.get("attachments"), patch.attachments)

        state["active_flow"] = patch.active_flow
        state["flows"][patch.flow_id] = flow
        state["events"] = self._append_event(state.get("events"), patch, flow)
        return state

    def merge_plan(
        self,
        current_state: dict[str, Any] | None,
        plan: StewardPlanResponse,
    ) -> dict[str, Any]:
        state = self._normalize_state(current_state)
        if plan.pending_flow_confirmation.status == "pending":
            state = self._merge_pending_flow_confirmation(
                state,
                plan.pending_flow_confirmation,
                next_action=plan.next_action,
            )
        for task in plan.tasks:
            state = self.merge_state(
                state,
                self._build_patch_from_task(
                    task,
                    linked_application_claim_id=self._resolve_application_claim_id(state),
                    attachments=self._resolve_task_attachments(plan, task.task_id),
                ),
            )
        return state

    @staticmethod
    def _normalize_state(current_state: dict[str, Any] | None) -> dict[str, Any]:
        source = current_state if isinstance(current_state, dict) else {}
        flows = source.get("flows") if isinstance(source.get("flows"), dict) else {}
        events = source.get("events") if isinstance(source.get("events"), list) else []
        return {
            "version": str(source.get("version") or "steward.flow_state.v2"),
            "active_flow": str(source.get("active_flow") or "").strip(),
            "flows": dict(flows),
            "pending_flow_confirmation": (
                dict(source.get("pending_flow_confirmation"))
                if isinstance(source.get("pending_flow_confirmation"), dict)
                else {
                    "status": "none",
                    "source_message": "",
                    "reason": "",
                    "candidate_flows": [],
                }
            ),
            "next_action": str(source.get("next_action") or "").strip(),
            "events": list(events),
        }

    def confirm_flow(
        self,
        current_state: dict[str, Any],
        flow_id: str,
    ) -> dict[str, Any]:
        state = self._normalize_state(current_state)
        if flow_id not in {"travel_application", "travel_reimbursement"}:
            return state

        flows = state["flows"] if isinstance(state.get("flows"), dict) else {}
        flow = dict(flows.get(flow_id) or {})
        flow["flow_id"] = flow_id
        flow["status"] = "collecting" if flow.get("missing_fields") else "ready_for_confirmation"
        flows[flow_id] = flow
        state["flows"] = flows
        state["active_flow"] = flow_id
        state["next_action"] = "continue_selected_flow"

        pending = dict(state.get("pending_flow_confirmation") or {})
        pending["status"] = "confirmed"
        pending["confirmed_flow_id"] = flow_id
        state["pending_flow_confirmation"] = pending
        state["events"] = self._append_flow_confirmation_event(state.get("events"), flow_id)
        return state

    def _merge_pending_flow_confirmation(
        self,
        state: dict[str, Any],
        pending: StewardPendingFlowConfirmation,
        *,
        next_action: str,
    ) -> dict[str, Any]:
        candidate_flows = [
            self._serialize_candidate_flow(candidate)
            for candidate in pending.candidate_flows
        ]
        state["version"] = "steward.flow_state.v2"
        state["active_flow"] = ""
        state["next_action"] = str(next_action or "confirm_flow")
        state["pending_flow_confirmation"] = {
            "status": pending.status,
            "source_message": pending.source_message,
            "reason": pending.reason,
            "candidate_flows": candidate_flows,
        }
        flows = state["flows"] if isinstance(state.get("flows"), dict) else {}
        for candidate in pending.candidate_flows:
            flow = dict(flows.get(candidate.flow_id) or {})
            flow.update(
                {
                    "flow_id": candidate.flow_id,
                    "intent": self._resolve_candidate_intent(candidate.flow_id),
                    "status": "pending_flow_confirmation",
                    "fields": self._normalize_fields(candidate.ontology_fields),
                    "missing_fields": self._normalize_missing_fields(candidate.missing_fields),
                    "confidence": candidate.confidence,
                    "evidence": [
                        {
                            "source": "pending_flow_confirmation",
                            "field": key,
                            "text": candidate.reason or pending.reason,
                        }
                        for key in candidate.ontology_fields
                    ],
                }
            )
            flows[candidate.flow_id] = flow
        state["flows"] = flows
        state["events"] = self._append_pending_flow_event(state.get("events"), pending)
        return state

    @staticmethod
    def _serialize_candidate_flow(candidate: StewardCandidateFlow) -> dict[str, Any]:
        return {
            "flow_id": candidate.flow_id,
            "label": candidate.label,
            "confidence": candidate.confidence,
            "reason": candidate.reason,
            "ontology_fields": dict(candidate.ontology_fields),
            "missing_fields": list(candidate.missing_fields),
        }

    @staticmethod
    def _resolve_candidate_intent(flow_id: str) -> str:
        return (
            "travel_application_create"
            if flow_id == "travel_application"
            else "travel_reimbursement_draft"
        )

    @staticmethod
    def _build_patch_from_task(
        task: StewardTask,
        *,
        linked_application_claim_id: str = "",
        attachments: list[dict[str, Any]] | None = None,
    ) -> StewardFlowStatePatch:
        if task.task_type == "expense_application":
            flow_id = "travel_application"
            intent = "travel_application_create"
            link_id = ""
        else:
            flow_id = "travel_reimbursement"
            intent = "travel_reimbursement_draft"
            link_id = linked_application_claim_id
        return StewardFlowStatePatch(
            active_flow=flow_id,
            flow_id=flow_id,
            intent=intent,
            status="collecting" if task.missing_fields else "ready_for_confirmation",
            fields=task.ontology_fields,
            missing_fields=task.missing_fields,
            linked_application_claim_id=link_id,
            attachments=attachments or [],
            evidence=[
                {
                    "source": "steward_plan",
                    "field": key,
                    "text": task.summary,
                }
                for key in task.ontology_fields
            ],
        )

    @staticmethod
    def _resolve_application_claim_id(state: dict[str, Any]) -> str:
        flows = state.get("flows") if isinstance(state.get("flows"), dict) else {}
        application_flow = flows.get("travel_application") if isinstance(flows, dict) else {}
        if not isinstance(application_flow, dict):
            return ""
        return str(application_flow.get("application_claim_id") or "").strip()

    @staticmethod
    def _resolve_task_attachments(
        plan: StewardPlanResponse,
        task_id: str,
    ) -> list[dict[str, Any]]:
        attachments: list[dict[str, Any]] = []
        for group in plan.attachment_groups:
            if group.target_task_id != task_id:
                continue
            for name in group.attachment_names:
                normalized = str(name or "").strip()
                if normalized:
                    attachments.append({"name": normalized, "source": "steward_attachment_group"})
        return attachments

    @staticmethod
    def _normalize_fields(fields: dict[str, Any]) -> dict[str, str]:
        normalized = normalize_ontology_form_values(fields)
        return {
            key: value
            for key, value in normalized.items()
            if key in CANONICAL_ONTOLOGY_FIELDS and str(value or "").strip()
        }

    @staticmethod
    def _normalize_missing_fields(fields: list[str]) -> list[str]:
        normalized: list[str] = []
        for field in fields:
            key = str(field or "").strip()
            if key in CANONICAL_ONTOLOGY_FIELDS and key not in normalized:
                normalized.append(key)
        return normalized

    @staticmethod
    def _merge_attachments(
        current_attachments: Any,
        incoming_attachments: list[dict[str, Any]],
    ) -> list[dict[str, Any]]:
        attachments = [
            dict(item)
            for item in current_attachments
            if isinstance(item, dict)
        ] if isinstance(current_attachments, list) else []
        seen = {
            str(item.get("file_id") or item.get("name") or "").strip()
            for item in attachments
            if str(item.get("file_id") or item.get("name") or "").strip()
        }
        for item in incoming_attachments:
            if not isinstance(item, dict):
                continue
            key = str(item.get("file_id") or item.get("name") or "").strip()
            if key and key in seen:
                continue
            attachments.append(dict(item))
            if key:
                seen.add(key)
        return attachments

    def _append_event(
        self,
        current_events: Any,
        patch: StewardFlowStatePatch,
        flow: dict[str, Any],
    ) -> list[dict[str, Any]]:
        events = [
            dict(item)
            for item in current_events
            if isinstance(item, dict)
        ] if isinstance(current_events, list) else []
        events.append(
            {
                "sequence": len(events) + 1,
                "flow_id": patch.flow_id,
                "active_flow": patch.active_flow,
                "intent": str(patch.intent or flow.get("intent") or "").strip(),
                "status": str(flow.get("status") or "").strip(),
                "fields": self._normalize_fields(patch.fields),
                "missing_fields": list(flow.get("missing_fields") or []),
                "evidence": [
                    dict(item)
                    for item in patch.evidence
                    if isinstance(item, dict)
                ],
            }
        )
        return events[-self.EVENT_LIMIT :]

    def _append_pending_flow_event(
        self,
        current_events: Any,
        pending: StewardPendingFlowConfirmation,
    ) -> list[dict[str, Any]]:
        events = [
            dict(item)
            for item in current_events
            if isinstance(item, dict)
        ] if isinstance(current_events, list) else []
        events.append(
            {
                "sequence": len(events) + 1,
                "flow_id": "",
                "active_flow": "",
                "intent": "pending_flow_confirmation",
                "status": pending.status,
                "fields": {},
                "missing_fields": [],
                "candidate_flows": [
                    self._serialize_candidate_flow(candidate)
                    for candidate in pending.candidate_flows
                ],
                "evidence": [
                    {
                        "source": "steward_plan",
                        "text": pending.source_message,
                    }
                ],
            }
        )
        return events[-self.EVENT_LIMIT :]

    def _append_flow_confirmation_event(
        self,
        current_events: Any,
        flow_id: str,
    ) -> list[dict[str, Any]]:
        events = [
            dict(item)
            for item in current_events
            if isinstance(item, dict)
        ] if isinstance(current_events, list) else []
        events.append(
            {
                "sequence": len(events) + 1,
                "flow_id": flow_id,
                "active_flow": flow_id,
                "intent": "flow_confirmed",
                "status": "confirmed",
                "fields": {},
                "missing_fields": [],
                "evidence": [{"source": "runtime_user_selection", "text": flow_id}],
            }
        )
        return events[-self.EVENT_LIMIT :]
