from __future__ import annotations

import logging

from sqlalchemy.orm import Session

from app.api.deps import CurrentUserContext
from app.schemas.expense_application_preview import (
    ExpenseApplicationPreviewDecisionCreate,
    ExpenseApplicationPreviewDecisionRead,
)
from app.schemas.ontology import OntologyParseResult, OntologyPermission
from app.schemas.reimbursement import (
    ExpenseApplicationPreviewActionPayload,
    ExpenseApplicationPreviewActionResponse,
    ExpenseApplicationPreviewActionResult,
)
from app.schemas.user_agent import UserAgentRequest
from app.services.application_system_estimate import (
    apply_application_system_estimate_to_facts,
)
from app.services.expense_application_memory import ExpenseApplicationMemoryService
from app.services.expense_application_preview_decisions import (
    ExpenseApplicationPreviewDecisionService,
    PreviewDecisionConflictError,
)
from app.services.expense_application_request_context import (
    build_trusted_expense_application_context,
)
from app.services.user_agent import UserAgentService

logger = logging.getLogger(__name__)


class ExpenseApplicationPreviewWorkflow:
    """编排费用申请预览的签发、消费和草稿续签。"""

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

    def issue(
        self,
        payload: ExpenseApplicationPreviewDecisionCreate,
        current_user: CurrentUserContext,
    ) -> ExpenseApplicationPreviewDecisionRead:
        run_id = f"application-preview-decision:{payload.request_id}"
        request = UserAgentRequest(
            run_id=run_id,
            user_id=current_user.username or current_user.name,
            message=payload.message,
            ontology=OntologyParseResult(run_id=run_id),
            context_json=build_trusted_expense_application_context(current_user),
            tool_payload={},
            selected_capability_codes=[],
            degraded=False,
            requires_confirmation=False,
        )
        try:
            facts = UserAgentService(self.db)._resolve_expense_application_facts(request)
            memory_applications = ExpenseApplicationMemoryService(
                self.db
            ).apply_active_transport_memory(facts, current_user)
            if memory_applications:
                # 交通方式会影响系统预估；记忆补空后必须在签名前重算派生字段。
                apply_application_system_estimate_to_facts(facts)
            issued = ExpenseApplicationPreviewDecisionService(self.db).issue(
                facts,
                current_user,
                conversation_id=payload.conversation_id or "",
                request_id=payload.request_id,
            )
            self.db.commit()
            self.db.refresh(issued.decision)
        except (PreviewDecisionConflictError, ValueError):
            self.db.rollback()
            raise

        return ExpenseApplicationPreviewDecisionRead(
            decision_id=issued.decision.id,
            decision_source=issued.decision.decision_source,
            expires_at=issued.decision.expires_at,
            application_preview={
                "fields": issued.fields,
                "memoryApplications": [
                    item.model_dump(mode="json") for item in memory_applications
                ],
                "decisionId": issued.decision.id,
                "decisionSource": issued.decision.decision_source,
                "decisionExpiresAt": issued.decision.expires_at.isoformat(),
            },
        )

    def execute(
        self,
        payload: ExpenseApplicationPreviewActionPayload,
        current_user: CurrentUserContext,
    ) -> ExpenseApplicationPreviewActionResponse:
        request = self._build_action_request(payload, current_user)
        try:
            user_agent_service = UserAgentService(self.db)
            facts = user_agent_service._resolve_expense_application_facts(request)
            resolved_step = user_agent_service._resolve_expense_application_step(request, facts)
            resolved_action = "save_draft" if resolved_step == "draft" else "submit"
            if payload.action_type and payload.action_type != resolved_action:
                raise ValueError("动作类型与申请内容不一致。")

            preview_decision_service = ExpenseApplicationPreviewDecisionService(self.db)
            preview_decision = None
            if payload.decision_id:
                preview_decision = preview_decision_service.require_for_action(
                    payload.decision_id,
                    current_user,
                    conversation_id=payload.conversation_id or "",
                    request_id=payload.request_id or "",
                    action_type=resolved_action,
                    final_values=facts,
                )
            else:
                preview_decision_service.reject_active_decision_bypass(
                    current_user,
                    conversation_id=payload.conversation_id or "",
                )

            consumed_preview_decision_id = (
                preview_decision.id if preview_decision is not None else ""
            )
            user_agent_response = user_agent_service._build_expense_application_response(
                request,
                risk_flags=[],
                learning_current_user=current_user,
                learning_preview_decision=preview_decision,
                learning_action_request_id=payload.request_id or "",
            )
        except (PreviewDecisionConflictError, ValueError):
            self.db.rollback()
            raise

        next_preview_decision = None
        if (
            preview_decision is not None
            and resolved_action == "save_draft"
            and user_agent_response.draft_payload is not None
        ):
            try:
                next_preview_decision = (
                    ExpenseApplicationPreviewDecisionService(self.db)
                    .issue(
                        facts,
                        current_user,
                        conversation_id=payload.conversation_id or "",
                        request_id=(
                            f"next:{consumed_preview_decision_id}:{payload.request_id or ''}"
                        )[:120],
                        decision_source="server_draft",
                    )
                    .decision
                )
                self.db.commit()
                self.db.refresh(next_preview_decision)
            except Exception as error:
                # 主业务动作已提交；续签失败只能降级，不能反转保存草稿结果。
                self.db.rollback()
                next_preview_decision = None
                logger.warning(
                    "费用申请草稿已保存，但后续预览决策签发失败：decision_id=%s error_type=%s",
                    consumed_preview_decision_id,
                    type(error).__name__,
                )

        learning_receipts = self._resolve_learning_receipts(
            consumed_preview_decision_id,
            current_user,
        )

        return ExpenseApplicationPreviewActionResponse(
            status="succeeded",
            conversation_id=payload.conversation_id,
            result=ExpenseApplicationPreviewActionResult(
                message=user_agent_response.answer,
                answer=user_agent_response.answer,
                suggested_actions=[
                    action.model_dump(mode="json")
                    for action in user_agent_response.suggested_actions
                ],
                risk_flags=user_agent_response.risk_flags,
                requires_confirmation=user_agent_response.requires_confirmation,
                draft_payload=(
                    user_agent_response.draft_payload.model_dump(mode="json")
                    if user_agent_response.draft_payload is not None
                    else None
                ),
                decision_id=(
                    next_preview_decision.id if next_preview_decision is not None else None
                ),
                decision_expires_at=(
                    next_preview_decision.expires_at
                    if next_preview_decision is not None
                    else None
                ),
                learning_receipts=[
                    item.model_dump(mode="json") for item in learning_receipts
                ],
            ),
        )

    def _resolve_learning_receipts(
        self,
        preview_decision_id: str,
        current_user: CurrentUserContext,
    ) -> list:
        try:
            with self.db.begin_nested():
                return ExpenseApplicationMemoryService(
                    self.db
                ).learning_receipts_for_preview_decision(
                    preview_decision_id,
                    current_user,
                )
        except Exception:
            logger.warning("个人记忆学习回执读取失败，本次申请动作保持成功。", exc_info=True)
            return []

    @staticmethod
    def _build_action_request(
        payload: ExpenseApplicationPreviewActionPayload,
        current_user: CurrentUserContext,
    ) -> UserAgentRequest:
        context_json = build_trusted_expense_application_context(
            current_user,
            payload.context_json,
        )
        if payload.action_type == "save_draft":
            context_json["application_action"] = "save_draft"
            context_json["application_save_mode"] = True
        elif payload.action_type == "submit":
            context_json.pop("application_action", None)
            context_json.pop("application_save_mode", None)

        run_id = f"application-preview-action:{payload.conversation_id or current_user.username}"
        return UserAgentRequest(
            run_id=run_id,
            user_id=current_user.username or current_user.name,
            message=payload.message,
            ontology=OntologyParseResult(
                scenario="expense",
                intent="operate",
                permission=OntologyPermission(
                    level="approval_required",
                    allowed=True,
                    reason="application preview fast action",
                ),
                confidence=1.0,
                run_id=run_id,
            ),
            context_json=context_json,
            tool_payload={},
            selected_capability_codes=[],
            degraded=False,
            requires_confirmation=False,
        )
