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 AgentAssetDomain, AgentAssetStatus, AgentAssetType
from app.models.agent_asset import AgentAsset, AgentAssetVersion
from app.repositories.agent_asset import AgentAssetRepository
from app.schemas.agent_asset import (
    AgentAssetRiskRuleGenerateRequest,
    AgentAssetRiskRuleRegenerateRequest,
)
from app.services.agent_asset_access import AgentAssetAccessScope
from app.services.agent_asset_rule_library import AgentAssetRuleLibraryManager
from app.services.agent_asset_spreadsheet import RISK_RULES_LIBRARY
from app.services.audit import AuditLogService
from app.services.risk_rule_dsl_validator import validate_risk_rule_draft
from app.services.risk_rule_generation import (
    BUSINESS_DOMAIN_LABELS,
    EXPENSE_BUSINESS_STAGE_LABELS,
    EXPENSE_RISK_CATEGORY_LABELS,
    RiskRuleGenerationService,
)
from app.services.risk_rule_generation_markdown import build_risk_rule_version_markdown
from app.services.risk_rule_scoring import apply_risk_score_to_draft, calculate_risk_rule_score
from app.services.runtime_chat import RuntimeChatService


class AgentAssetRiskRuleRegenerationService:
    """重新把自然语言草稿或修订草稿解释为可执行 JSON 风险规则。"""

    def __init__(
        self,
        db: Session,
        *,
        rule_library_manager: AgentAssetRuleLibraryManager | None = None,
        runtime_chat_service: RuntimeChatService | None = None,
        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.rule_library_manager = rule_library_manager or AgentAssetRuleLibraryManager()
        self.generator = RiskRuleGenerationService(
            db,
            rule_library_manager=self.rule_library_manager,
            runtime_chat_service=runtime_chat_service,
        )
        self.audit_service = AuditLogService(db)

    def regenerate(
        self,
        asset_id: str,
        body: AgentAssetRiskRuleRegenerateRequest,
        *,
        tenant_id: str | None = None,
        actor: str,
        request_id: str | None = None,
    ) -> AgentAsset:
        asset = self._resolve_json_risk_asset(asset_id)
        if str(asset.published_version or "").strip():
            return self._regenerate_revision_draft(
                asset,
                body,
                tenant_id=tenant_id,
                actor=actor,
                request_id=request_id,
            )
        return self._regenerate_unpublished_draft(
            asset,
            body,
            tenant_id=tenant_id,
            actor=actor,
            request_id=request_id,
        )

    def _regenerate_unpublished_draft(
        self,
        asset: AgentAsset,
        body: AgentAssetRiskRuleRegenerateRequest,
        *,
        tenant_id: str | None,
        actor: str,
        request_id: str | None,
    ) -> AgentAsset:
        if asset.status not in {AgentAssetStatus.DRAFT.value, AgentAssetStatus.FAILED.value}:
            raise ValueError("只有未上线草稿或生成失败规则可以重新生成。")

        before = self._snapshot(asset)
        config = dict(asset.config_json or {})
        request = self._build_generation_request(asset, config, body.model_dump(exclude_unset=True))
        payload, risk_score = self._compile_payload(
            request,
            tenant_id=tenant_id,
            actor=actor,
            created_at=asset.created_at,
        )
        rule_code = self._stable_rule_code(asset, payload)
        payload["rule_code"] = rule_code
        file_name = f"{rule_code}.json"
        self.rule_library_manager.write_rule_library_json(
            library=RISK_RULES_LIBRARY,
            file_name=file_name,
            payload=payload,
        )

        version = str(asset.working_version or asset.current_version or "v0.1.0")
        now = datetime.now(UTC).isoformat()
        self._upsert_version(
            asset,
            version=version,
            content=build_risk_rule_version_markdown(payload),
            change_note="重新生成自然语言风险规则草稿。",
            actor=actor,
        )
        config.update(self._config_from_payload(payload, risk_score=risk_score, request=request))
        config["tenant_id"] = str(tenant_id or "").strip()
        config.update(
            {
                "generation_status": "completed",
                "generation_completed_at": now,
                "last_operation": {"action": "regenerate", "actor": actor, "at": now},
            }
        )
        asset.code = rule_code
        asset.name = str(payload["name"])
        asset.description = str(payload["description"])
        asset.domain = str(request.get("business_domain") or AgentAssetDomain.EXPENSE.value)
        asset.scenario_json = [
            str(payload.get("risk_category") or BUSINESS_DOMAIN_LABELS[asset.domain])
        ]
        asset.status = AgentAssetStatus.DRAFT.value
        asset.current_version = version
        asset.working_version = version
        asset.config_json = config
        self.db.add(asset)
        self.db.flush()
        self.audit_service.log_action(
            actor=actor,
            action="regenerate_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 _regenerate_revision_draft(
        self,
        asset: AgentAsset,
        body: AgentAssetRiskRuleRegenerateRequest,
        *,
        tenant_id: str | None,
        actor: str,
        request_id: str | None,
    ) -> AgentAsset:
        revision = self._resolve_revision_draft(asset)
        revision_version = str(revision.get("version") or "").strip()
        before = self._snapshot(asset)
        config = dict(asset.config_json or {})
        request = self._build_generation_request(
            asset,
            config,
            body.model_dump(exclude_unset=True),
            base=(
                revision.get("generation_request")
                if isinstance(revision.get("generation_request"), dict)
                else {}
            ),
        )
        payload, risk_score = self._compile_payload(
            request,
            tenant_id=tenant_id,
            actor=actor,
            created_at=datetime.now(UTC),
        )
        payload["rule_code"] = str(asset.code or payload["rule_code"]).strip()
        payload["enabled"] = False
        payload.setdefault("metadata", {})["revision_version"] = revision_version
        payload["metadata"]["revision_base_version"] = revision.get("base_version")
        file_name = f"{payload['rule_code']}.{revision_version.replace('.', '_')}.json"
        self.rule_library_manager.write_rule_library_json(
            library=RISK_RULES_LIBRARY,
            file_name=file_name,
            payload=payload,
        )

        now = datetime.now(UTC).isoformat()
        revision.update(
            {
                "status": "generated",
                "generation_status": "completed",
                "generation_request": request,
                "generated_by": actor,
                "generated_at": now,
                "rule_code": payload["rule_code"],
                "rule_document": {
                    "file_name": file_name,
                    "storage_key": f"rules/{RISK_RULES_LIBRARY}/{file_name}",
                },
                "risk_score": risk_score["score"],
                "risk_level": risk_score["level"],
                "risk_level_label": risk_score["level_label"],
                "flow_diagram_svg": payload.get("flow_diagram_svg"),
                "business_explanation": payload.get("metadata", {}).get("business_explanation"),
            }
        )
        config["revision_draft"] = revision
        config["tenant_id"] = str(tenant_id or "").strip()
        config["last_operation"] = {
            "action": "regenerate_revision",
            "actor": actor,
            "at": now,
            "target_version": revision_version,
        }
        asset.working_version = revision_version
        asset.config_json = config
        self._upsert_version(
            asset,
            version=revision_version,
            content=build_risk_rule_version_markdown(payload),
            change_note=str(revision.get("change_reason") or "重新生成修订草稿"),
            actor=actor,
        )
        self.db.add(asset)
        self.db.flush()
        self.audit_service.log_action(
            actor=actor,
            action="regenerate_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 _compile_payload(
        self,
        request: dict[str, Any],
        *,
        tenant_id: str | None,
        actor: str,
        created_at: datetime | None,
    ) -> tuple[dict[str, Any], dict[str, Any]]:
        body = AgentAssetRiskRuleGenerateRequest.model_validate(request)
        domain = body.business_domain.value
        natural_language = self.generator._clean_text(body.natural_language)
        rule_title = self.generator._clean_text(body.rule_title)
        requires_attachment = bool(body.requires_attachment)
        business_stage = self.generator._normalize_business_stage(body.business_stage, domain)
        business_stage_label = EXPENSE_BUSINESS_STAGE_LABELS.get(business_stage, "费用报销")
        expense_category = self.generator._normalize_expense_category(body.expense_category, domain)
        expense_category_label = EXPENSE_RISK_CATEGORY_LABELS.get(expense_category or "", "")
        fields = self.generator._resolve_fields(natural_language, domain=domain)
        draft = self.generator._compile_with_model(
            tenant_id=tenant_id,
            natural_language=natural_language,
            domain=domain,
            business_stage=business_stage,
            business_stage_label=business_stage_label,
            expense_category=expense_category,
            expense_category_label=expense_category_label,
            fields=fields,
        ) or self.generator._build_fallback_draft(
            natural_language=natural_language,
            domain=domain,
            expense_category_label=expense_category_label,
            risk_level="medium",
            fields=fields,
        )
        draft = validate_risk_rule_draft(draft, fields=fields, natural_language=natural_language)
        draft = self.generator._align_draft_fields(
            draft,
            natural_language=natural_language,
            risk_level="medium",
            fields=fields,
        )
        draft = validate_risk_rule_draft(draft, fields=fields, natural_language=natural_language)
        risk_score = calculate_risk_rule_score(
            natural_language=natural_language,
            draft=draft,
            fields=fields,
            expense_category=expense_category,
            expense_category_label=expense_category_label,
            requires_attachment=requires_attachment,
        )
        risk_level = str(risk_score["level"])
        draft = apply_risk_score_to_draft(draft, risk_score)
        payload = self.generator._build_rule_payload(
            draft,
            natural_language=natural_language,
            domain=domain,
            business_stage=business_stage,
            business_stage_label=business_stage_label,
            expense_category=expense_category,
            expense_category_label=expense_category_label,
            risk_level=risk_level,
            fields=fields,
            created_at=created_at or datetime.now(UTC),
            actor=actor,
            requires_attachment=requires_attachment,
            rule_title=rule_title,
            risk_score=risk_score,
        )
        return payload, risk_score

    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 _resolve_revision_draft(self, asset: AgentAsset) -> dict[str, Any]:
        config = dict(asset.config_json or {})
        revision = config.get("revision_draft")
        if not isinstance(revision, dict):
            raise ValueError("已上线规则需要先创建修订版本，再重新生成。")
        revision_version = str(revision.get("version") or "").strip()
        if not revision_version or revision_version != str(asset.working_version or "").strip():
            raise ValueError("修订草稿版本与当前工作版本不一致。")
        if revision.get("status") not in {"draft", "generated", "failed"}:
            raise ValueError("当前修订草稿状态不允许重新生成。")
        return dict(revision)

    @staticmethod
    def _build_generation_request(
        asset: AgentAsset,
        config: dict[str, Any],
        updates: dict[str, Any],
        *,
        base: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        source = base if isinstance(base, dict) else config.get("generation_request")
        merged = dict(source if isinstance(source, dict) else {})
        merged.setdefault("business_domain", asset.domain or AgentAssetDomain.EXPENSE.value)
        merged.setdefault("business_stage", config.get("business_stage") or "reimbursement")
        merged.setdefault("expense_category", config.get("expense_category"))
        merged.setdefault("rule_title", asset.name)
        merged.setdefault("natural_language", asset.description)
        merged.setdefault("requires_attachment", bool(config.get("requires_attachment")))
        for key, value in updates.items():
            merged[key] = value
        return merged

    @staticmethod
    def _stable_rule_code(asset: AgentAsset, payload: dict[str, Any]) -> str:
        current = str(asset.code or "").strip()
        if current and ".generating_" not in current:
            return current
        return str(payload.get("rule_code") or current).strip()

    @staticmethod
    def _config_from_payload(
        payload: dict[str, Any],
        *,
        risk_score: dict[str, Any],
        request: dict[str, Any],
    ) -> dict[str, Any]:
        file_name = f"{payload['rule_code']}.json"
        return {
            "severity": risk_score["level"],
            "risk_score": risk_score["score"],
            "risk_level": risk_score["level"],
            "risk_level_label": risk_score["level_label"],
            "risk_score_detail": risk_score,
            "requires_attachment": bool(request.get("requires_attachment")),
            "detail_mode": "json_risk",
            "business_stage": request.get("business_stage"),
            "business_stage_label": payload.get("metadata", {}).get("business_stage_label"),
            "expense_category": request.get("expense_category"),
            "expense_category_label": payload.get("metadata", {}).get("expense_category_label"),
            "risk_category": payload.get("risk_category"),
            "rule_library": RISK_RULES_LIBRARY,
            "rule_document": {
                "file_name": file_name,
                "storage_key": f"rules/{RISK_RULES_LIBRARY}/{file_name}",
            },
            "ontology_signal": payload.get("ontology_signal"),
            "evaluator": payload.get("evaluator"),
            "generated_by": "natural_language",
            "source_ref": "自然语言风险规则",
            "generation_request": request,
            "flow_diagram_svg": payload.get("flow_diagram_svg"),
        }

    def _upsert_version(
        self,
        asset: AgentAsset,
        *,
        version: str,
        content: str,
        change_note: str,
        actor: str,
    ) -> None:
        existing = self.repository.get_version(asset.id, version)
        if existing is None:
            self.db.add(
                AgentAssetVersion(
                    tenant_id=asset.tenant_id,
                    scope=asset.scope,
                    asset_id=asset.id,
                    version=version,
                    content=content,
                    content_type="markdown",
                    change_note=change_note,
                    created_by=actor,
                )
            )
            return
        existing.content = content
        existing.change_note = change_note
        existing.created_by = actor
        self.db.add(existing)

    @staticmethod
    def _snapshot(asset: AgentAsset) -> dict[str, Any]:
        return {
            "id": asset.id,
            "code": asset.code,
            "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 {},
        }
