"""把类型化风险处置解析为当前 Agent 发布样本的可信标签。"""

from __future__ import annotations

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models.agent_asset import AgentAsset
from app.models.agent_asset_release_telemetry import (
    AgentAssetReleaseLabel,
    AgentAssetReleaseObservation,
)
from app.models.risk_disposition import RiskDispositionEvent
from app.models.risk_observation import RiskObservation
from app.services.agent_asset_release_telemetry import (
    AgentAssetReleaseTelemetryService,
)
from app.services.agent_asset_release_telemetry_crypto import release_source_fingerprints

_RELEASE_STAGES = {"shadow", "canary", "active"}
_BUSINESS_STAGES = {"expense_application", "reimbursement"}


class AgentAssetReleaseDispositionLabelService:
    """在当前 release/stage/version 内定位同租户、同单据、同规则样本。"""

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

    def record_current_labels(
        self,
        *,
        tenant_id: str,
        disposition_event_id: str,
    ) -> list[AgentAssetReleaseLabel]:
        normalized_tenant = _required(tenant_id, "tenant_id", 64)
        event = self.db.scalar(
            select(RiskDispositionEvent).where(
                RiskDispositionEvent.id == disposition_event_id,
                RiskDispositionEvent.tenant_id == normalized_tenant,
            )
        )
        if event is None:
            raise LookupError("Risk disposition event not found.")
        if event.action not in {"confirm", "false_positive"}:
            return []
        risk_observation = self.db.scalar(
            select(RiskObservation).where(
                RiskObservation.id == event.observation_id,
                RiskObservation.tenant_id == normalized_tenant,
            )
        )
        if risk_observation is None:
            raise LookupError("Risk observation not found.")
        rule_code = _rule_code(risk_observation)
        if not rule_code:
            return []

        asset = self._asset(normalized_tenant, rule_code)
        state = _release_state(asset)
        release_id = str(state.get("release_id") or "").strip()
        stage = str(state.get("stage") or "").strip().lower()
        version = str(state.get("candidate_version") or "").strip()
        if not release_id or stage not in _RELEASE_STAGES or not version:
            return []
        source_fingerprints = release_source_fingerprints(
            tenant_id=normalized_tenant,
            source_key=_required(risk_observation.claim_id, "claim_id", 100),
            rule_code=rule_code,
        )
        statement = select(AgentAssetReleaseObservation).where(
            AgentAssetReleaseObservation.tenant_id == normalized_tenant,
            AgentAssetReleaseObservation.asset_id == asset.id,
            AgentAssetReleaseObservation.release_id == release_id,
            AgentAssetReleaseObservation.stage == stage,
            AgentAssetReleaseObservation.version == version,
            AgentAssetReleaseObservation.rule_code == rule_code,
            AgentAssetReleaseObservation.source_fingerprint.in_(source_fingerprints),
            AgentAssetReleaseObservation.runtime_status == "completed",
            (
                AgentAssetReleaseObservation.candidate_hit.is_(True)
                | AgentAssetReleaseObservation.baseline_hit.is_(True)
            ),
        )
        business_stage = str(risk_observation.control_stage or "").strip().lower()
        if business_stage in _BUSINESS_STAGES:
            statement = statement.where(
                AgentAssetReleaseObservation.business_stage == business_stage
            )
        observations = list(
            self.db.scalars(
                statement.order_by(
                    AgentAssetReleaseObservation.created_at.asc(),
                    AgentAssetReleaseObservation.id.asc(),
                )
            ).all()
        )
        telemetry = AgentAssetReleaseTelemetryService(self.db)
        return [
            telemetry.record_risk_disposition_label(
                tenant_id=normalized_tenant,
                observation_id=observation.id,
                disposition_event_id=event.id,
            )
            for observation in observations
        ]

    def _asset(self, tenant_id: str, rule_code: str) -> AgentAsset:
        asset = self.db.scalar(
            select(AgentAsset)
            .where(
                AgentAsset.code == rule_code,
                (
                    ((AgentAsset.scope == "tenant") & (AgentAsset.tenant_id == tenant_id))
                    | ((AgentAsset.scope == "platform") & (AgentAsset.tenant_id == "platform"))
                ),
            )
            .order_by(AgentAsset.scope.desc())
        )
        if asset is None:
            raise LookupError("Agent asset not found.")
        configured_tenant = str((asset.config_json or {}).get("tenant_id") or "").strip()
        if asset.scope == "tenant" and configured_tenant not in {"", tenant_id}:
            raise LookupError("Agent asset not found.")
        if asset.scope == "platform" and configured_tenant != tenant_id:
            raise LookupError("Agent asset not found.")
        return asset


def _rule_code(observation: RiskObservation) -> str:
    value = str((observation.decision_trace_json or {}).get("rule_code") or "").strip()
    if not value and observation.policy_refs_json:
        value = str(observation.policy_refs_json[0] or "").strip()
    return value


def _release_state(asset: AgentAsset) -> dict:
    config = asset.config_json if isinstance(asset.config_json, dict) else {}
    state = config.get("release_guard")
    return dict(state) if isinstance(state, dict) else {}


def _required(value: object, field: str, maximum: int) -> str:
    normalized = str(value or "").strip()
    if not normalized or len(normalized) > maximum:
        raise ValueError(f"{field} is required and must be at most {maximum} characters.")
    return normalized
