from __future__ import annotations

from types import SimpleNamespace
from typing import Any

from app.models.agent_asset import AgentAsset
from app.services.agent_asset_rule_library import AgentAssetRuleLibraryManager
from app.services.agent_asset_spreadsheet import RISK_RULES_LIBRARY
from app.services.risk_rule_scoring import RISK_SCORE_MODEL_VERSION, calculate_risk_rule_score


def backfill_missing_risk_rule_score(
    asset: AgentAsset,
    *,
    rule_library_manager: AgentAssetRuleLibraryManager | None = None,
) -> bool:
    config_json = dict(asset.config_json or {})
    if str(config_json.get("detail_mode") or "").strip().lower() != "json_risk":
        return False
    if _has_current_score(config_json):
        return False

    manager = rule_library_manager or AgentAssetRuleLibraryManager()
    library = str(config_json.get("rule_library") or RISK_RULES_LIBRARY).strip() or RISK_RULES_LIBRARY
    file_name = _resolve_rule_file_name(asset, config_json)
    if not file_name:
        return False

    manifest = manager.read_rule_library_json(library=library, file_name=file_name)
    if _has_current_score(manifest) or _has_current_score(manifest.get("metadata")):
        score = _read_existing_score(manifest)
    else:
        score = _calculate_score(asset, manifest, config_json)
        _apply_score_to_manifest(manifest, score)
        manager.write_rule_library_json(library=library, file_name=file_name, payload=manifest)

    _apply_score_to_config(config_json, manifest, score)
    asset.config_json = config_json
    return True


def _resolve_rule_file_name(asset: AgentAsset, config_json: dict[str, Any]) -> str:
    rule_document = config_json.get("rule_document")
    if isinstance(rule_document, dict):
        file_name = str(rule_document.get("file_name") or "").strip()
        if file_name:
            return file_name
    code = str(asset.code or "").strip()
    return f"{code}.json" if code else ""


def _calculate_score(
    asset: AgentAsset,
    manifest: dict[str, Any],
    config_json: dict[str, Any],
) -> dict[str, Any]:
    metadata = manifest.get("metadata") if isinstance(manifest.get("metadata"), dict) else {}
    params = manifest.get("params") if isinstance(manifest.get("params"), dict) else {}
    fields = _read_fields(manifest)
    field_keys = _read_field_keys(manifest, fields)
    draft = {
        "template_key": manifest.get("template_key") or params.get("template_key"),
        "field_keys": field_keys,
        "description": manifest.get("description") or asset.description,
        "condition_summary": metadata.get("condition_summary") or params.get("condition_summary"),
        "formula": params.get("formula"),
        "message_template": params.get("message_template"),
        "conditions": params.get("conditions") if isinstance(params.get("conditions"), list) else [],
        "keywords": params.get("keywords") if isinstance(params.get("keywords"), list) else [],
        "exception_keywords": params.get("exception_keywords")
        if isinstance(params.get("exception_keywords"), list)
        else [],
        "flow": metadata.get("flow") if isinstance(metadata.get("flow"), dict) else {},
    }
    if isinstance(params.get("rule_ir"), dict):
        draft["rule_ir"] = params["rule_ir"]

    generation_request = (
        config_json.get("generation_request")
        if isinstance(config_json.get("generation_request"), dict)
        else {}
    )
    natural_language = str(
        metadata.get("natural_language")
        or params.get("natural_language")
        or generation_request.get("natural_language")
        or manifest.get("description")
        or asset.description
        or ""
    )
    expense_category = str(
        metadata.get("expense_category") or config_json.get("expense_category") or ""
    ).strip() or None
    expense_category_label = str(
        metadata.get("expense_category_label")
        or config_json.get("expense_category_label")
        or manifest.get("risk_category")
        or ""
    ).strip()
    requires_attachment = bool(
        manifest.get("requires_attachment") or config_json.get("requires_attachment")
    )
    return 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,
    )


def _read_fields(manifest: dict[str, Any]) -> list[Any]:
    inputs = manifest.get("inputs") if isinstance(manifest.get("inputs"), dict) else {}
    rows = inputs.get("fields") if isinstance(inputs.get("fields"), list) else []
    return [
        SimpleNamespace(
            key=str(row.get("key") or "").strip(),
            label=str(row.get("label") or "").strip(),
            field_type=str(row.get("type") or "").strip(),
            source=str(row.get("source") or "").strip(),
        )
        for row in rows
        if isinstance(row, dict) and str(row.get("key") or "").strip()
    ]


def _read_field_keys(manifest: dict[str, Any], fields: list[Any]) -> list[str]:
    params = manifest.get("params") if isinstance(manifest.get("params"), dict) else {}
    raw_keys = params.get("field_keys") or params.get("required_fields")
    if isinstance(raw_keys, list):
        keys = [str(item or "").strip() for item in raw_keys if str(item or "").strip()]
        if keys:
            return keys
    return [str(getattr(field, "key", "") or "").strip() for field in fields]


def _apply_score_to_manifest(manifest: dict[str, Any], score: dict[str, Any]) -> None:
    level = str(score.get("level") or "medium")
    manifest["severity"] = level
    manifest["risk_score"] = int(score.get("score") or 0)
    manifest["risk_level"] = level
    manifest["risk_level_label"] = str(score.get("level_label") or "")
    manifest["risk_score_detail"] = score

    outcomes = manifest.setdefault("outcomes", {})
    if isinstance(outcomes, dict):
        fail = outcomes.setdefault("fail", {})
        if isinstance(fail, dict):
            fail["severity"] = level
            fail["risk_score"] = int(score.get("score") or 0)

    metadata = manifest.setdefault("metadata", {})
    if isinstance(metadata, dict):
        metadata["risk_score"] = int(score.get("score") or 0)
        metadata["risk_level"] = level
        metadata["risk_level_label"] = str(score.get("level_label") or "")
        metadata["risk_score_model"] = score.get("model")
        metadata["risk_score_detail"] = score


def _apply_score_to_config(
    config_json: dict[str, Any],
    manifest: dict[str, Any],
    score: dict[str, Any],
) -> None:
    level = str(score.get("level") or manifest.get("risk_level") or "medium")
    config_json["severity"] = level
    config_json["risk_score"] = int(score.get("score") or 0)
    config_json["risk_level"] = level
    config_json["risk_level_label"] = str(score.get("level_label") or "")
    config_json["risk_score_detail"] = score


def _has_score(value: Any) -> bool:
    if not isinstance(value, dict):
        return False
    try:
        score = int(value.get("risk_score") if value.get("risk_score") is not None else value.get("score"))
    except (TypeError, ValueError):
        return False
    return 0 <= score <= 100


def _has_current_score(value: Any) -> bool:
    if not _has_score(value):
        return False
    return _read_score_model(value) == RISK_SCORE_MODEL_VERSION


def _read_score_model(value: Any) -> str:
    if not isinstance(value, dict):
        return ""
    detail = value.get("risk_score_detail")
    if isinstance(detail, dict):
        model = str(detail.get("model") or "").strip()
        if model:
            return model
    metadata = value.get("metadata")
    if isinstance(metadata, dict):
        detail = metadata.get("risk_score_detail")
        if isinstance(detail, dict):
            model = str(detail.get("model") or "").strip()
            if model:
                return model
        model = str(metadata.get("risk_score_model") or "").strip()
        if model:
            return model
    return str(value.get("risk_score_model") or value.get("model") or "").strip()


def _read_existing_score(manifest: dict[str, Any]) -> dict[str, Any]:
    metadata = manifest.get("metadata") if isinstance(manifest.get("metadata"), dict) else {}
    detail = metadata.get("risk_score_detail")
    if isinstance(detail, dict) and _has_score(detail):
        return dict(detail)
    detail = manifest.get("risk_score_detail")
    if isinstance(detail, dict) and _has_score(detail):
        return dict(detail)
    score = int(metadata.get("risk_score") or manifest.get("risk_score") or 0)
    level = str(metadata.get("risk_level") or manifest.get("risk_level") or "medium")
    return {
        "score": score,
        "level": level,
        "level_label": str(metadata.get("risk_level_label") or manifest.get("risk_level_label") or ""),
        "model": metadata.get("risk_score_model"),
    }
