from __future__ import annotations

from datetime import UTC, datetime
from typing import Any

from sqlalchemy.orm import Session

from app.core.agent_enums import AgentAssetStatus, AgentAssetType
from app.models.agent_asset import AgentAsset, AgentAssetVersion
from app.schemas.agent_asset import AgentAssetRiskRuleGenerateRequest
from app.services.agent_asset_access import platform_resource_identity, tenant_resource_identity
from app.services.agent_asset_spreadsheet import RISK_RULES_LIBRARY
from app.services.audit import AuditLogService
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 RiskRuleGenerationJobService:
    """把自然语言风险规则生成拆成可追踪的后台任务。"""

    def __init__(
        self,
        db: Session,
        *,
        rule_library_manager: Any | None = None,
        runtime_chat_service: RuntimeChatService | None = None,
    ) -> None:
        self.db = db
        self.generator = RiskRuleGenerationService(
            db,
            rule_library_manager=rule_library_manager,
            runtime_chat_service=runtime_chat_service,
        )
        self.audit_service = AuditLogService(db)

    def enqueue_rule_asset_generation(
        self,
        body: AgentAssetRiskRuleGenerateRequest,
        *,
        tenant_id: str | None = None,
        actor: str,
        request_id: str | None = None,
    ) -> str:
        domain = self._validate_domain(body)
        natural_language = self._validate_natural_language(body)
        rule_title = self._validate_rule_title(body)
        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 "", "")

        created_at = datetime.now(UTC)
        rule_code = self._build_placeholder_rule_code(
            domain=domain,
            expense_category=expense_category,
            created_at=created_at,
        )
        category_label = expense_category_label or BUSINESS_DOMAIN_LABELS[domain]
        display_name = rule_title or self.generator._infer_rule_name(natural_language)
        file_name = f"{rule_code}.json"
        normalized_tenant = str(tenant_id or "").strip()
        resource_tenant_id, resource_scope = (
            tenant_resource_identity(normalized_tenant)
            if normalized_tenant
            else platform_resource_identity()
        )

        asset = AgentAsset(
            tenant_id=resource_tenant_id,
            scope=resource_scope,
            asset_type=AgentAssetType.RULE.value,
            code=rule_code,
            name=display_name,
            description=natural_language,
            domain=domain,
            scenario_json=[category_label],
            owner=actor,
            reviewer=None,
            status=AgentAssetStatus.GENERATING.value,
            current_version=None,
            published_version=None,
            working_version=None,
            config_json={
                "severity": "medium",
                "risk_score_status": "pending",
                "enabled": True,
                "requires_attachment": requires_attachment,
                "tag": "风险规则",
                "detail_mode": "json_risk",
                "business_stage": business_stage,
                "business_stage_label": business_stage_label,
                "expense_category": expense_category,
                "expense_category_label": expense_category_label,
                "risk_category": category_label,
                "rule_library": RISK_RULES_LIBRARY,
                "rule_document": {
                    "file_name": file_name,
                    "storage_key": f"rules/{RISK_RULES_LIBRARY}/{file_name}",
                },
                "generated_by": "natural_language",
                "tenant_id": str(tenant_id or "").strip(),
                "generation_status": AgentAssetStatus.GENERATING.value,
                "generation_started_at": created_at.isoformat(),
                "generation_request": self._dump_generation_request(body),
                "last_operation": {
                    "action": "generate",
                    "actor": actor,
                    "at": created_at.isoformat(),
                },
            },
        )
        self.db.add(asset)
        self.db.flush()
        self.audit_service.log_action(
            actor=actor,
            action="enqueue_agent_asset_risk_rule_generation",
            resource_type=AgentAssetType.RULE.value,
            resource_id=asset.id,
            before_json=None,
            after_json={
                "rule_code": rule_code,
                "domain": domain,
                "business_stage": business_stage,
                "expense_category": expense_category,
            },
            request_id=request_id,
        )
        self.db.refresh(asset)
        return asset.id

    def complete_rule_asset_generation(
        self,
        asset_id: str,
        body: AgentAssetRiskRuleGenerateRequest,
        *,
        tenant_id: str | None = None,
        actor: str,
        request_id: str | None = None,
    ) -> None:
        try:
            asset = self.db.get(AgentAsset, asset_id)
            if (
                asset is None
                or not self._matches_tenant(asset, tenant_id)
                or asset.status != AgentAssetStatus.GENERATING.value
            ):
                return
            self._complete_rule_asset(
                asset,
                body,
                tenant_id=tenant_id,
                actor=actor,
                request_id=request_id,
            )
        except Exception as exc:  # noqa: BLE001 - 后台任务必须把失败写回资产状态
            self.mark_generation_failed(
                asset_id,
                error_message=str(exc) or exc.__class__.__name__,
                actor=actor,
                tenant_id=tenant_id,
                request_id=request_id,
            )

    def mark_generation_failed(
        self,
        asset_id: str,
        *,
        error_message: str,
        actor: str,
        tenant_id: str | None = None,
        request_id: str | None = None,
    ) -> None:
        asset = self.db.get(AgentAsset, asset_id)
        if asset is None or not self._matches_tenant(asset, tenant_id):
            return

        config_json = dict(asset.config_json or {})
        config_json.update(
            {
                "generation_status": AgentAssetStatus.FAILED.value,
                "generation_error": error_message[:1000],
                "generation_failed_at": datetime.now(UTC).isoformat(),
                "last_operation": {
                    "action": "generation_failed",
                    "actor": actor,
                    "at": datetime.now(UTC).isoformat(),
                },
            }
        )
        asset.status = AgentAssetStatus.FAILED.value
        asset.config_json = config_json
        self.db.add(asset)
        self.db.flush()
        self.audit_service.log_action(
            actor=actor,
            action="fail_agent_asset_risk_rule_generation",
            resource_type=AgentAssetType.RULE.value,
            resource_id=asset.id,
            before_json=None,
            after_json={"generation_error": error_message[:1000]},
            request_id=request_id,
        )

    def _complete_rule_asset(
        self,
        asset: AgentAsset,
        body: AgentAssetRiskRuleGenerateRequest,
        *,
        tenant_id: str | None,
        actor: str,
        request_id: str | None,
    ) -> None:
        domain = self._validate_domain(body)
        natural_language = self._validate_natural_language(body)
        rule_title = self._validate_rule_title(body)
        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 "", "")
        created_at = asset.created_at or datetime.now(UTC)
        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 = self.generator._align_draft_fields(
            draft,
            natural_language=natural_language,
            risk_level="medium",
            fields=fields,
        )
        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,
            actor=actor,
            requires_attachment=requires_attachment,
            rule_title=rule_title,
            risk_score=risk_score,
        )
        rule_code = str(payload["rule_code"])
        file_name = f"{rule_code}.json"
        self.generator.rule_library_manager.write_rule_library_json(
            library=RISK_RULES_LIBRARY,
            file_name=file_name,
            payload=payload,
        )

        config_json = {
            "severity": risk_level,
            "risk_score": risk_score["score"],
            "risk_level": risk_level,
            "risk_level_label": risk_score["level_label"],
            "risk_score_detail": risk_score,
            "enabled": True,
            "requires_attachment": requires_attachment,
            "tag": "风险规则",
            "detail_mode": "json_risk",
            "business_stage": business_stage,
            "business_stage_label": business_stage_label,
            "expense_category": expense_category,
            "expense_category_label": 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",
            "tenant_id": str(tenant_id or "").strip(),
            "source_ref": "自然语言风险规则",
            "generation_status": "completed",
            "generation_completed_at": datetime.now(UTC).isoformat(),
            "last_operation": {
                "action": "create",
                "actor": actor,
                "at": datetime.now(UTC).isoformat(),
            },
        }

        asset.code = rule_code
        asset.name = str(payload["name"])
        asset.description = str(payload["description"])
        asset.domain = domain
        asset.scenario_json = [str(payload.get("risk_category") or BUSINESS_DOMAIN_LABELS[domain])]
        asset.status = AgentAssetStatus.DRAFT.value
        asset.current_version = "v0.1.0"
        asset.published_version = None
        asset.working_version = "v0.1.0"
        asset.config_json = config_json
        self.db.add(asset)
        self.db.add(
            AgentAssetVersion(
                tenant_id=asset.tenant_id,
                scope=asset.scope,
                asset_id=asset.id,
                version="v0.1.0",
                content=build_risk_rule_version_markdown(payload),
                content_type="markdown",
                change_note="通过自然语言新建风险规则草稿。",
                created_by=actor,
            )
        )
        self.db.flush()
        self.audit_service.log_action(
            actor=actor,
            action="complete_agent_asset_risk_rule_generation",
            resource_type=AgentAssetType.RULE.value,
            resource_id=asset.id,
            before_json=None,
            after_json={
                "rule_code": rule_code,
                "risk_level": risk_level,
                "risk_score": risk_score["score"],
                "domain": domain,
                "business_stage": business_stage,
                "expense_category": expense_category,
                "requires_attachment": requires_attachment,
            },
            request_id=request_id,
        )

    def _validate_domain(self, body: AgentAssetRiskRuleGenerateRequest) -> str:
        domain = body.business_domain.value
        if domain not in BUSINESS_DOMAIN_LABELS:
            raise ValueError("当前仅支持报销、应收、应付业务域的新建风险规则。")
        return domain

    @staticmethod
    def _matches_tenant(asset: AgentAsset, tenant_id: str | None) -> bool:
        normalized = str(tenant_id or "").strip()
        if not normalized:
            return asset.scope == "platform" and asset.tenant_id == "platform"
        return asset.scope == "tenant" and asset.tenant_id == normalized

    def _validate_natural_language(self, body: AgentAssetRiskRuleGenerateRequest) -> str:
        natural_language = self.generator._clean_text(body.natural_language)
        if len(natural_language) < 8:
            raise ValueError("请至少输入 8 个字的风险规则描述。")
        return natural_language

    def _validate_rule_title(self, body: AgentAssetRiskRuleGenerateRequest) -> str:
        rule_title = self.generator._clean_text(body.rule_title)
        if rule_title and len(rule_title) < 2:
            raise ValueError("规则标题至少需要 2 个字。")
        return rule_title

    @staticmethod
    def _build_placeholder_rule_code(
        *,
        domain: str,
        expense_category: str | None,
        created_at: datetime,
    ) -> str:
        domain_slug = {"expense": "expense", "ar": "ar", "ap": "ap"}[domain]
        category_slug = f".{expense_category}" if expense_category else ""
        return f"risk.{domain_slug}{category_slug}.generating_{created_at.strftime('%Y%m%d%H%M%S%f')}"

    @staticmethod
    def _dump_generation_request(body: AgentAssetRiskRuleGenerateRequest) -> dict[str, Any]:
        return body.model_dump(mode="json")
