# [DEF:backend.src.core.migration.risk_assessor:Module]
# @TIER: STANDARD
# @SEMANTICS: migration, dry_run, risk, scoring
# @PURPOSE: Risk evaluation helpers for migration pre-flight reporting.
# @LAYER: Core
# @RELATION: USED_BY -> backend.src.core.migration.dry_run_orchestrator

from typing import Any, Dict, List

from ..superset_client import SupersetClient


# [DEF:index_by_uuid:Function]
# @PURPOSE: Build UUID-index from normalized objects.
def index_by_uuid(objects: List[Dict[str, Any]]) -> Dict[str, Dict[str, Any]]:
    indexed: Dict[str, Dict[str, Any]] = {}
    for obj in objects:
        uuid = obj.get("uuid")
        if uuid:
            indexed[str(uuid)] = obj
    return indexed
# [/DEF:index_by_uuid:Function]


# [DEF:extract_owner_identifiers:Function]
# @PURPOSE: Normalize owner payloads for stable comparison.
def extract_owner_identifiers(owners: Any) -> List[str]:
    if not isinstance(owners, list):
        return []
    ids: List[str] = []
    for owner in owners:
        if isinstance(owner, dict):
            if owner.get("username"):
                ids.append(str(owner["username"]))
            elif owner.get("id") is not None:
                ids.append(str(owner["id"]))
        elif owner is not None:
            ids.append(str(owner))
    return sorted(set(ids))
# [/DEF:extract_owner_identifiers:Function]


# [DEF:build_risks:Function]
# @PURPOSE: Build risk list from computed diffs and target catalog state.
def build_risks(
    source_objects: Dict[str, List[Dict[str, Any]]],
    target_objects: Dict[str, List[Dict[str, Any]]],
    diff: Dict[str, Dict[str, List[Dict[str, Any]]]],
    target_client: SupersetClient,
) -> List[Dict[str, Any]]:
    risks: List[Dict[str, Any]] = []
    for object_type in ("dashboards", "charts", "datasets"):
        for item in diff[object_type]["update"]:
            risks.append({
                "code": "overwrite_existing",
                "severity": "medium",
                "object_type": object_type[:-1],
                "object_uuid": item["uuid"],
                "message": f"Object will be updated in target: {item.get('title') or item['uuid']}",
            })

    target_dataset_uuids = set(index_by_uuid(target_objects["datasets"]).keys())
    _, target_databases = target_client.get_databases(query={"columns": ["uuid"]})
    target_database_uuids = {str(item.get("uuid")) for item in target_databases if item.get("uuid")}

    for dataset in source_objects["datasets"]:
        db_uuid = dataset.get("database_uuid")
        if db_uuid and str(db_uuid) not in target_database_uuids:
            risks.append({
                "code": "missing_datasource",
                "severity": "high",
                "object_type": "dataset",
                "object_uuid": dataset.get("uuid"),
                "message": f"Target datasource is missing for dataset {dataset.get('title') or dataset.get('uuid')}",
            })

    for chart in source_objects["charts"]:
        ds_uuid = chart.get("dataset_uuid")
        if ds_uuid and str(ds_uuid) not in target_dataset_uuids:
            risks.append({
                "code": "breaking_reference",
                "severity": "high",
                "object_type": "chart",
                "object_uuid": chart.get("uuid"),
                "message": f"Chart references dataset not found on target: {ds_uuid}",
            })

    source_dash = index_by_uuid(source_objects["dashboards"])
    target_dash = index_by_uuid(target_objects["dashboards"])
    for item in diff["dashboards"]["update"]:
        source_obj = source_dash.get(item["uuid"])
        target_obj = target_dash.get(item["uuid"])
        if not source_obj or not target_obj:
            continue
        source_owners = extract_owner_identifiers(source_obj.get("owners"))
        target_owners = extract_owner_identifiers(target_obj.get("owners"))
        if source_owners and target_owners and source_owners != target_owners:
            risks.append({
                "code": "owner_mismatch",
                "severity": "low",
                "object_type": "dashboard",
                "object_uuid": item["uuid"],
                "message": f"Owner mismatch for dashboard {item.get('title') or item['uuid']}",
            })
    return risks
# [/DEF:build_risks:Function]


# [DEF:score_risks:Function]
# @PURPOSE: Aggregate risk list into score and level.
def score_risks(risk_items: List[Dict[str, Any]]) -> Dict[str, Any]:
    weights = {"high": 25, "medium": 10, "low": 5}
    score = min(100, sum(weights.get(item.get("severity", "low"), 5) for item in risk_items))
    level = "low" if score < 25 else "medium" if score < 60 else "high"
    return {"score": score, "level": level, "items": risk_items}
# [/DEF:score_risks:Function]


# [/DEF:backend.src.core.migration.risk_assessor:Module]
