"""Govdoc Bridge — 存储适配器。

将 govdoc_engine 执行结果写入 govdoc_runs / govdoc_rule_results /
govdoc_report_artifacts 表，并更新 leaudit_documents 状态。
"""

from __future__ import annotations

import json
from datetime import datetime, timezone
from typing import Any

from fastapi_common.fastapi_common_logger import logger
from fastapi_common.fastapi_common_sqlalchemy.database import GetAsyncSession
from sqlalchemy import text

log = logger


_RUN_RESULT_FALLBACK_KEYS = {
    "total_score": ["totalScore", "score"],
    "passed_count": ["passedCount", "passed_count"],
    "failed_count": ["failedCount", "failed_count"],
    "skipped_count": ["skippedCount", "skipped_count"],
    "result_status": ["resultStatus", "result_status"],
    "result_summary_json": ["resultSummaryJson", "result_summary_json"],
}


def _pick_value(payload: dict[str, Any], *keys: str) -> Any:
    for key in keys:
        if key in payload:
            return payload[key]
    return None


def _to_text_payload(value: Any) -> str | None:
    if value is None:
        return None
    if isinstance(value, str):
        return value
    if isinstance(value, (dict, list, tuple, bool, int, float)):
        return json.dumps(value, ensure_ascii=False)
    return str(value)


class StorageAdapter:
    """Govdoc 结果持久化适配器。

    将 bridge 层产生的结构化结果写入 PostgreSQL。
    使用原生 SQL (text) 以保持与现有 leaudit_bridge/storage_adapter 风格一致。
    """

    # ── Run 状态 ─────────────────────────────────────────

    async def CreateRun(self, RunData: dict[str, Any]) -> int:
        """创建 govdoc_runs 记录，返回 run_id。"""
        async with GetAsyncSession() as session:
            result = await session.execute(
                text(
                    """INSERT INTO govdoc_runs
                    (document_id, document_file_id, run_no, trigger_source,
                     trigger_user_id, status, phase, created_at, updated_at)
                    VALUES (:document_id, :document_file_id, :run_no, :trigger_source,
                     :trigger_user_id, 'pending', 'parsing', now(), now())
                    RETURNING id"""
                ),
                {
                    "document_id": RunData["documentId"],
                    "document_file_id": RunData.get("documentFileId"),
                    "run_no": RunData.get("runNo", 1),
                    "trigger_source": RunData.get("triggerSource", "manual"),
                    "trigger_user_id": RunData.get("triggerUserId"),
                },
            )
            row = result.fetchone()
            run_id = row[0] if row else 0
            await session.commit()
        log.info(f"[Govdoc] Run created: runId={run_id}, documentId={RunData['documentId']}")
        return run_id

    async def UpdateRunStatus(
        self,
        RunId: int,
        Status: str,
        Phase: str | None = None,
        **Extra: Any,
    ) -> None:
        """更新 run 状态和阶段。

        兼容调用方传 ``phase=`` 或 ``Phase=``，避免因大小写不一致导致阶段不落库。
        """
        set_clauses = ["status = :status", "updated_at = now()"]
        params: dict[str, Any] = {"rid": RunId, "status": Status}

        if Phase is None:
            Phase = _pick_value(Extra, "phase", "Phase")

        if Phase is not None:
            set_clauses.append("phase = :phase")
            params["phase"] = Phase

        if Status == "completed" or Status == "failed":
            set_clauses.append("finished_at = :finished_at")
            params["finished_at"] = datetime.now(timezone.utc)
        elif Status == "processing":
            set_clauses.append("started_at = COALESCE(started_at, :started_at)")
            params["started_at"] = datetime.now(timezone.utc)

        async with GetAsyncSession() as session:
            await session.execute(
                text(f"UPDATE govdoc_runs SET {', '.join(set_clauses)} WHERE id = :rid"),
                params,
            )
            await session.commit()
        log.info(f"[Govdoc] Run status updated: runId={RunId}, status={Status}")

    async def UpdateRunResult(self, RunId: int, Summary: dict[str, Any]) -> None:
        """写入 run 结果汇总字段（含 rules_path / structure / outline）。"""
        rules_path = _pick_value(Summary, "rulesPath", "rules_path")
        set_clauses = [
            "total_score = :total_score",
            "passed_count = :passed_count",
            "failed_count = :failed_count",
            "skipped_count = :skipped_count",
            "result_status = :result_status",
            "result_summary_json = :result_summary_json",
            "updated_at = now()",
        ]
        params: dict[str, Any] = {
            "rid": RunId,
            "total_score": _pick_value(Summary, *_RUN_RESULT_FALLBACK_KEYS["total_score"]),
            "passed_count": _pick_value(Summary, *_RUN_RESULT_FALLBACK_KEYS["passed_count"]) or 0,
            "failed_count": _pick_value(Summary, *_RUN_RESULT_FALLBACK_KEYS["failed_count"]) or 0,
            "skipped_count": _pick_value(Summary, *_RUN_RESULT_FALLBACK_KEYS["skipped_count"]) or 0,
            "result_status": _pick_value(Summary, *_RUN_RESULT_FALLBACK_KEYS["result_status"]),
            "result_summary_json": _pick_value(Summary, *_RUN_RESULT_FALLBACK_KEYS["result_summary_json"]),
        }
        if rules_path:
            set_clauses.append("rules_path = :rules_path")
            params["rules_path"] = rules_path

        async with GetAsyncSession() as session:
            await session.execute(
                text(f"UPDATE govdoc_runs SET {', '.join(set_clauses)} WHERE id = :rid"),
                params,
            )
            await session.commit()
        log.info(f"[Govdoc] Run result saved: runId={RunId}")

    async def UpdateRunError(self, RunId: int, ErrorMessage: str) -> None:
        """记录运行失败的错误信息。"""
        async with GetAsyncSession() as session:
            await session.execute(
                text(
                    """UPDATE govdoc_runs SET
                    status = 'failed',
                    error_message = :error_message,
                    finished_at = now(),
                    updated_at = now()
                    WHERE id = :rid"""
                ),
                {"rid": RunId, "error_message": ErrorMessage},
            )
            await session.commit()
        log.error(f"[Govdoc] Run failed: runId={RunId}, error={ErrorMessage[:200]}")

    # ── 规则结果 ─────────────────────────────────────────

    async def SaveRuleResults(self, RunId: int, Results: list[dict[str, Any]]) -> None:
        """批量写入 govdoc_rule_results。"""
        if not Results:
            return

        async with GetAsyncSession() as session:
            for row in Results:
                await session.execute(
                    text(
                        """INSERT INTO govdoc_rule_results
                        (run_id, rule_id, rule_name, severity, category,
                         message, suggestion, actual, expected, evidence,
                         paragraph_index, paragraph_text, location_path,
                         result, skip_reason, score, created_at, updated_at)
                        VALUES (:run_id, :rule_id, :rule_name, :severity, :category,
                         :message, :suggestion, :actual, :expected, :evidence,
                         :paragraph_index, :paragraph_text, :location_path,
                         :result, :skip_reason, :score, now(), now())"""
                    ),
                    {
                        "run_id": RunId,
                        "rule_id": row.get("ruleId"),
                        "rule_name": row.get("ruleName"),
                        "severity": row.get("severity"),
                        "category": row.get("category"),
                        "message": row.get("message"),
                        "suggestion": row.get("suggestion"),
                        "actual": _to_text_payload(row.get("actual")),
                        "expected": _to_text_payload(row.get("expected")),
                        "evidence": _to_text_payload(row.get("evidence")),
                        "paragraph_index": row.get("paragraphIndex"),
                        "paragraph_text": row.get("paragraphText"),
                        "location_path": row.get("locationPath"),
                        "result": row.get("result", "pass"),
                        "skip_reason": _pick_value(row, "skipReason", "skip_reason"),
                        "score": row.get("score"),
                    },
                )
            await session.commit()
        log.info(f"[Govdoc] Rule results saved: runId={RunId}, count={len(Results)}")

    # ── 报告产物 ─────────────────────────────────────────

    async def SaveArtifacts(self, RunId: int, Artifacts: list[dict[str, Any]]) -> None:
        """批量写入 govdoc_report_artifacts。"""
        if not Artifacts:
            return

        async with GetAsyncSession() as session:
            for row in Artifacts:
                await session.execute(
                    text(
                        """INSERT INTO govdoc_report_artifacts
                        (run_id, artifact_type, file_name, file_ext, mime_type,
                         file_size, sha256, oss_url, storage_provider, description,
                         created_at, updated_at)
                        VALUES (:run_id, :artifact_type, :file_name, :file_ext, :mime_type,
                         :file_size, :sha256, :oss_url, :storage_provider, :description,
                         now(), now())"""
                    ),
                    {
                        "run_id": RunId,
                        "artifact_type": row.get("artifactType"),
                        "file_name": row.get("fileName"),
                        "file_ext": row.get("fileExt"),
                        "mime_type": row.get("mimeType"),
                        "file_size": row.get("fileSize"),
                        "sha256": row.get("sha256"),
                        "oss_url": row.get("ossUrl"),
                        "storage_provider": row.get("storageProvider"),
                        "description": row.get("description"),
                    },
                )
            await session.commit()
        log.info(f"[Govdoc] Artifacts saved: runId={RunId}, count={len(Artifacts)}")

    # ── 文档状态 ─────────────────────────────────────────

    async def UpdateDocumentStatus(self, DocumentId: int, ProcessingStatus: str, RunId: int | None = None) -> None:
        """更新 leaudit_documents 的处理状态和当前 run_id。"""
        async with GetAsyncSession() as session:
            if RunId is not None:
                await session.execute(
                    text(
                        """UPDATE leaudit_documents SET
                        processing_status = :s, current_run_id = :rid, updated_at = now()
                        WHERE id = :did"""
                    ),
                    {"s": ProcessingStatus, "rid": RunId, "did": DocumentId},
                )
            else:
                await session.execute(
                    text(
                        """UPDATE leaudit_documents SET
                        processing_status = :s, updated_at = now()
                        WHERE id = :did"""
                    ),
                    {"s": ProcessingStatus, "did": DocumentId},
                )
            await session.commit()
        log.info(f"[Govdoc] Document status updated: documentId={DocumentId}, status={ProcessingStatus}")
