"""Risk-policy feature module.

This module owns:
- Default policy values
- Policy-file loading
- Safe validation/merge behavior
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Optional

from .reason_codes import (
    ENV_BASELINE_MISSING,
    ENV_SCANNER_PARTIAL_RESULTS,
    ENV_THREAT_DB_STALE,
    HEUR_NEW_EXEC_IN_SENSITIVE_PATH,
    HEUR_RECENT_SUSPICIOUS_EXTENSION,
    HEUR_UNEXPECTED_MODIFICATION_NEAR_WALLET,
    IOC_HASH_KNOWN_MALICIOUS,
)

DEFAULT_SUSPICIOUS_EXTENSIONS = {
    ".exe",
    ".dll",
    ".scr",
    ".ps1",
    ".vbs",
    ".js",
    ".sh",
    ".bat",
    ".cmd",
    ".pyc",
    ".class",
    ".apk",
}

DEFAULT_SENSITIVE_PATH_TOKENS = {
    ".config",
    ".wallet",
    "wallet",
    "metamask",
    "phantom",
    "solflare",
    "keystore",
    ".ssh",
}


def default_risk_policy() -> dict:
    """Return baseline policy used when no policy file is provided/valid."""
    return {
        "version": "2026.02.13.1",
        "thresholds": {"medium_min": 35, "high_min": 70},
        "suspicious_extensions": sorted(DEFAULT_SUSPICIOUS_EXTENSIONS),
        "sensitive_path_tokens": sorted(DEFAULT_SENSITIVE_PATH_TOKENS),
        "reason_config": {
            IOC_HASH_KNOWN_MALICIOUS: {
                "weight": 60,
                "severity": "critical",
                "title": "Known malicious hash match",
            },
            HEUR_NEW_EXEC_IN_SENSITIVE_PATH: {
                "weight": 35,
                "severity": "high",
                "title": "New executable/script in sensitive path",
            },
            HEUR_RECENT_SUSPICIOUS_EXTENSION: {
                "weight": 20,
                "severity": "medium",
                "title": "Recent file with suspicious extension",
            },
            HEUR_UNEXPECTED_MODIFICATION_NEAR_WALLET: {
                "weight": 25,
                "severity": "high",
                "title": "Unexpected modification near wallet paths",
            },
            ENV_BASELINE_MISSING: {
                "weight": 8,
                "severity": "low",
                "title": "Baseline missing",
            },
            ENV_THREAT_DB_STALE: {
                "weight": 10,
                "severity": "medium",
                "title": "Threat database stale",
            },
            ENV_SCANNER_PARTIAL_RESULTS: {
                "weight": 12,
                "severity": "medium",
                "title": "Scanner partial results",
            },
        },
    }


def _load_json(path: Path) -> Optional[dict]:
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        return None


def load_risk_policy(path: Path) -> dict:
    """Load risk policy from disk and merge onto defaults.

    Merge behavior is intentionally conservative: only known keys are accepted,
    and invalid values fall back to defaults.
    """
    default = default_risk_policy()
    loaded = _load_json(path)
    if not loaded or not isinstance(loaded, dict):
        return default

    out = dict(default)

    thresholds = loaded.get("thresholds", {})
    if isinstance(thresholds, dict):
        medium = thresholds.get("medium_min", default["thresholds"]["medium_min"])
        high = thresholds.get("high_min", default["thresholds"]["high_min"])
        try:
            medium_i = int(medium)
            high_i = int(high)
        except (TypeError, ValueError):
            medium_i = default["thresholds"]["medium_min"]
            high_i = default["thresholds"]["high_min"]
        if 0 <= medium_i <= high_i <= 100:
            out["thresholds"] = {"medium_min": medium_i, "high_min": high_i}

    extensions = loaded.get("suspicious_extensions")
    if isinstance(extensions, list):
        parsed_exts = {str(e).strip().lower() for e in extensions if str(e).strip()}
        if parsed_exts:
            out["suspicious_extensions"] = sorted(parsed_exts)

    tokens = loaded.get("sensitive_path_tokens")
    if isinstance(tokens, list):
        parsed_tokens = {str(t).strip().lower() for t in tokens if str(t).strip()}
        if parsed_tokens:
            out["sensitive_path_tokens"] = sorted(parsed_tokens)

    merged_reason_cfg = dict(default["reason_config"])
    reason_cfg = loaded.get("reason_config")
    if isinstance(reason_cfg, dict):
        for code, cfg in reason_cfg.items():
            if code not in merged_reason_cfg or not isinstance(cfg, dict):
                continue
            current = dict(merged_reason_cfg[code])
            if "title" in cfg and isinstance(cfg["title"], str) and cfg["title"].strip():
                current["title"] = cfg["title"].strip()
            if "severity" in cfg and isinstance(cfg["severity"], str) and cfg["severity"].strip():
                current["severity"] = cfg["severity"].strip().lower()
            if "weight" in cfg:
                try:
                    w = int(cfg["weight"])
                    if 0 <= w <= 100:
                        current["weight"] = w
                except (TypeError, ValueError):
                    pass
            merged_reason_cfg[code] = current
    out["reason_config"] = merged_reason_cfg

    if isinstance(loaded.get("version"), str) and loaded["version"].strip():
        out["version"] = loaded["version"].strip()
    return out
