from __future__ import annotations

from datetime import UTC, datetime
from typing import Any

from sqlalchemy.orm import Session

from app.api.deps import CurrentUserContext
from app.core.agent_enums import AgentAssetStatus, AgentAssetType
from app.models.agent_asset import AgentAsset, AgentAssetVersion
from app.repositories.agent_asset import AgentAssetRepository
from app.schemas.agent_asset import (
    AgentAssetRiskRuleDraftUpdate,
    AgentAssetRiskRuleRevisionCreate,
)
from app.services.agent_asset_access import AgentAssetAccessScope
from app.services.audit import AuditLogService
from app.services.risk_rule_generation_ontology import EXPENSE_RISK_CATEGORY_LABELS


class AgentAssetRiskRuleRevisionService:
    """风险规则草稿编辑与已发布规则修订草稿服务。"""

    def __init__(
        self,
        db: Session,
        *,
        current_user: CurrentUserContext | None = None,
    ) -> None:
        self.db = db
        self.access_scope = (
            AgentAssetAccessScope.from_user(current_user) if current_user is not None else None
        )
        self.repository = AgentAssetRepository(db, access_scope=self.access_scope)
        self.audit_service = AuditLogService(db)

    def update_unpublished_draft(
        self,
        asset_id: str,
        body: AgentAssetRiskRuleDraftUpdate,
        *,
        actor: str,
        request_id: str | None = None,
    ) -> AgentAsset:
        asset = self._resolve_json_risk_asset(asset_id)
        if str(asset.published_version or "").strip() or asset.status not in {
            AgentAssetStatus.DRAFT.value,
            AgentAssetStatus.FAILED.value,
        }:
            raise PermissionError("只有未上线草稿或生成失败的风险规则可以直接编辑。")

        before = self._snapshot(asset)
        config = dict(asset.config_json or {})
        request = self._merged_generation_request(config, body.model_dump(exclude_unset=True))
        self._apply_edit_payload(asset, config, request, actor=actor, action="update_draft")
        self.db.add(asset)
        self.db.flush()
        self.audit_service.log_action(
            actor=actor,
            action="update_risk_rule_draft",
            resource_type=AgentAssetType.RULE.value,
            resource_id=asset.id,
            before_json=before,
            after_json=self._snapshot(asset),
            request_id=request_id,
        )
        return asset

    def create_revision_draft(
        self,
        asset_id: str,
        body: AgentAssetRiskRuleRevisionCreate,
        *,
        actor: str,
        request_id: str | None = None,
    ) -> AgentAsset:
        asset = self._resolve_json_risk_asset(asset_id)
        if not str(asset.published_version or "").strip():
            raise ValueError("未上线规则不需要创建修订版本，请直接编辑草稿。")
        if asset.status not in {AgentAssetStatus.ACTIVE.value, AgentAssetStatus.DISABLED.value}:
            raise ValueError("只有已上线或已下线规则可以创建修订版本。")

        before = self._snapshot(asset)
        config = dict(asset.config_json or {})
        request = self._merged_generation_request(config, body.model_dump(exclude_unset=True))
        revision_version = self._next_revision_version(asset)
        now = datetime.now(UTC).isoformat()
        config["revision_draft"] = {
            "version": revision_version,
            "base_version": asset.published_version,
            "status": "draft",
            "change_reason": body.change_reason,
            "generation_request": request,
            "created_by": actor,
            "created_at": now,
        }
        config["last_operation"] = {
            "action": "create_revision",
            "actor": actor,
            "at": now,
            "target_version": revision_version,
        }
        asset.working_version = revision_version
        asset.config_json = config
        self.db.add(asset)
        self.db.add(
            AgentAssetVersion(
                tenant_id=asset.tenant_id,
                scope=asset.scope,
                asset_id=asset.id,
                version=revision_version,
                content=self._build_revision_content(asset, config),
                content_type="markdown",
                change_note=body.change_reason,
                created_by=actor,
            )
        )
        self.db.flush()
        self.audit_service.log_action(
            actor=actor,
            action="create_risk_rule_revision",
            resource_type=AgentAssetType.RULE.value,
            resource_id=asset.id,
            before_json=before,
            after_json=self._snapshot(asset),
            request_id=request_id,
        )
        return asset

    def _resolve_json_risk_asset(self, asset_id: str) -> AgentAsset:
        asset = self.repository.get(asset_id)
        if asset is None:
            raise FileNotFoundError("风险规则不存在。")
        if self.access_scope is not None:
            self.access_scope.require_write(asset)
        config = asset.config_json or {}
        if (
            asset.asset_type != AgentAssetType.RULE.value
            or config.get("detail_mode") != "json_risk"
        ):
            raise ValueError("当前资产不是自然语言风险规则。")
        return asset

    def _apply_edit_payload(
        self,
        asset: AgentAsset,
        config: dict[str, Any],
        request: dict[str, Any],
        *,
        actor: str,
        action: str,
    ) -> None:
        now = datetime.now(UTC).isoformat()
        rule_title = str(request.get("rule_title") or asset.name or "").strip()
        natural_language = str(request.get("natural_language") or asset.description or "").strip()
        expense_category = str(
            request.get("expense_category") or config.get("expense_category") or ""
        ).strip()
        category_label = EXPENSE_RISK_CATEGORY_LABELS.get(
            expense_category, config.get("risk_category") or ""
        )
        asset.name = rule_title or asset.name
        asset.description = natural_language or asset.description
        if category_label:
            asset.scenario_json = [category_label]
        config.update(
            {
                "expense_category": expense_category or None,
                "expense_category_label": category_label,
                "risk_category": category_label or config.get("risk_category"),
                "requires_attachment": bool(request.get("requires_attachment")),
                "generation_request": request,
                "generation_status": "draft_updated",
                "last_operation": {"action": action, "actor": actor, "at": now},
            }
        )
        asset.config_json = config

    @staticmethod
    def _merged_generation_request(
        config: dict[str, Any], updates: dict[str, Any]
    ) -> dict[str, Any]:
        base = (
            config.get("generation_request")
            if isinstance(config.get("generation_request"), dict)
            else {}
        )
        merged = dict(base)
        for key, value in updates.items():
            if key == "change_reason":
                continue
            merged[key] = value
        merged.setdefault("business_domain", "expense")
        merged.setdefault("business_stage", config.get("business_stage") or "reimbursement")
        merged.setdefault("expense_category", config.get("expense_category"))
        merged.setdefault("rule_title", config.get("rule_title") or "")
        merged.setdefault("natural_language", "")
        merged.setdefault("requires_attachment", bool(config.get("requires_attachment")))
        return merged

    def _next_revision_version(self, asset: AgentAsset) -> str:
        base = str(
            asset.working_version
            or asset.current_version
            or asset.published_version
            or "v0.1.0"
        )
        major, minor, patch = self._parse_version(base)
        existing = {version.version for version in self.repository.list_versions(asset.id)}
        while True:
            patch += 1
            candidate = f"v{major}.{minor}.{patch}"
            if candidate not in existing:
                return candidate

    @staticmethod
    def _parse_version(value: str) -> tuple[int, int, int]:
        parts = str(value or "").strip().removeprefix("v").split(".")
        numbers = [int(part) if part.isdigit() else 0 for part in parts[:3]]
        padded = (numbers + [0, 0, 0])[:3]
        return padded[0], padded[1], padded[2]

    @staticmethod
    def _build_revision_content(asset: AgentAsset, config: dict[str, Any]) -> str:
        revision = (
            config.get("revision_draft")
            if isinstance(config.get("revision_draft"), dict)
            else {}
        )
        request = (
            revision.get("generation_request")
            if isinstance(revision.get("generation_request"), dict)
            else {}
        )
        return "\n".join(
            [
                f"# {asset.name} 修订草稿",
                "",
                f"- 基线版本：{revision.get('base_version') or ''}",
                f"- 修订版本：{revision.get('version') or ''}",
                f"- 修订原因：{revision.get('change_reason') or ''}",
                f"- 规则描述：{request.get('natural_language') or asset.description}",
            ]
        )

    @staticmethod
    def _snapshot(asset: AgentAsset) -> dict[str, Any]:
        return {
            "id": asset.id,
            "name": asset.name,
            "description": asset.description,
            "status": asset.status,
            "current_version": asset.current_version,
            "published_version": asset.published_version,
            "working_version": asset.working_version,
            "config_json": asset.config_json or {},
        }
