"""Loads SDK signatures from YAML and LibChecker JSON rule directories."""
import hashlib
import json
import re
from pathlib import Path
from typing import List, Optional

try:
    import yaml
except ImportError:
    yaml = None


LIBCHECKER_TYPE_TO_DETECTION_KEY: dict[str, str] = {
    "native-libs": "native_libs",
    "services-libs": "manifest_services",
    "receivers-libs": "manifest_receivers",
    "activities-libs": "manifest_activities",
    "providers-libs": "manifest_providers",
    "actions-libs": "manifest_actions",
    "static-libs": "package_prefixes",
}


def _parse_yaml_simple(text: str) -> dict:
    """Minimal YAML parser for simple flat/nested structures (fallback if PyYAML not available)."""
    import re
    result = {}
    stack = [result]
    indent_stack = [-1]
    current_list_key = None

    for raw_line in text.splitlines():
        if not raw_line.strip() or raw_line.strip().startswith("#"):
            continue
        indent = len(raw_line) - len(raw_line.lstrip())
        line = raw_line.strip()

        while len(indent_stack) > 1 and indent <= indent_stack[-1]:
            indent_stack.pop()
            stack.pop()

        if line.startswith("- "):
            val = line[2:].strip().strip('"').strip("'")
            parent = stack[-1]
            if isinstance(parent, list):
                parent.append(val)
            elif isinstance(parent, dict) and current_list_key:
                if current_list_key not in parent:
                    parent[current_list_key] = []
                parent[current_list_key].append(val)
        elif ":" in line:
            key, _, val = line.partition(":")
            key = key.strip()
            val = val.strip().strip('"').strip("'")
            parent = stack[-1]
            if isinstance(parent, dict):
                current_list_key = key
                if val == "" or val is None:
                    parent[key] = {}
                    stack.append(parent[key])
                    indent_stack.append(indent)
                else:
                    parent[key] = val
    return result


class SignatureLoader:
    def __init__(self, sig_dir: Path):
        self.sig_dir = sig_dir
        self.db_version = "unknown"

    @staticmethod
    def _slug(value: str) -> str:
        slug = re.sub(r"[^a-z0-9]+", "_", (value or "").lower()).strip("_")
        return slug or "unknown"

    @staticmethod
    def _libchecker_signature_id(type_dir: str, rule_name: str) -> str:
        digest = hashlib.sha1(f"{type_dir}:{rule_name}".encode("utf-8")).hexdigest()[:10]
        head = f"lc_{type_dir.replace('-', '_')}_{SignatureLoader._slug(rule_name)}"
        base = f"{head}_{digest}"
        if len(base) <= 110:
            return base
        return f"{head[:99]}_{digest}"

    @staticmethod
    def _iter_libchecker_rule_files(type_root: Path) -> list[Path]:
        files = sorted(type_root.rglob("*.json"))
        return [
            path for path in files
            if "regex" not in path.parts and not path.stem.startswith("regex_")
        ]

    @staticmethod
    def _pick_locale_data(payload: dict) -> dict:
        entries = payload.get("data") or []
        if not isinstance(entries, list):
            return {}
        for locale in ("en", "zh-Hans"):
            for entry in entries:
                if (entry or {}).get("locale") == locale:
                    return (entry or {}).get("data") or {}
        if entries:
            return (entries[0] or {}).get("data") or {}
        return {}

    @staticmethod
    def _rule_name_from_relpath(type_dir: str, rel_file: Path) -> str:
        stem = rel_file.with_suffix("")
        if type_dir == "actions-libs":
            return stem.name
        return str(stem).replace("/", ".")

    @classmethod
    def _build_libchecker_signature(cls, type_dir: str, rel_file: Path, payload: dict) -> dict:
        detection_key = LIBCHECKER_TYPE_TO_DETECTION_KEY[type_dir]
        rule_name = cls._rule_name_from_relpath(type_dir, rel_file)
        locale_data = cls._pick_locale_data(payload)
        source_link = str(locale_data.get("source_link") or "").strip()
        notes = "Loaded from LibChecker-Rules format."
        if source_link:
            notes += f" Source: {source_link}"
        return {
            "id": cls._libchecker_signature_id(type_dir, rule_name),
            "name": str(locale_data.get("label") or rule_name),
            "category": type_dir.replace("-", "_"),
            "vendor": str(locale_data.get("dev_team") or "Unknown"),
            "description": str(locale_data.get("description") or "Imported from LibChecker rules."),
            "detection": {detection_key: [rule_name]},
            "risk": {
                "data_collection": [],
                "privacy_risk": "unknown",
                "notes": notes,
            },
        }

    def _load_yaml_signatures(self, categories: Optional[List[str]] = None) -> list[dict]:
        signatures: list[dict] = []
        for cat_dir in sorted(self.sig_dir.iterdir()):
            if not cat_dir.is_dir() or cat_dir.name.startswith("_"):
                continue
            if cat_dir.name in LIBCHECKER_TYPE_TO_DETECTION_KEY:
                # These folders are loaded by LibChecker JSON path below.
                continue
            if categories and cat_dir.name not in categories:
                continue
            yaml_files = [*sorted(cat_dir.glob("*.yaml")), *sorted(cat_dir.glob("*.yml"))]
            for yaml_file in yaml_files:
                try:
                    text = yaml_file.read_text(encoding="utf-8")
                    sig = yaml.safe_load(text) if yaml else _parse_yaml_simple(text)
                    if sig and "id" in sig:
                        signatures.append(sig)
                except Exception as e:
                    print(f"[WARN] Failed to load {yaml_file}: {e}")
        return signatures

    def _load_libchecker_signatures(self, categories: Optional[List[str]] = None) -> list[dict]:
        signatures: list[dict] = []
        for type_dir in LIBCHECKER_TYPE_TO_DETECTION_KEY:
            type_root = self.sig_dir / type_dir
            if not type_root.exists() or not type_root.is_dir():
                continue
            normalized_category = type_dir.replace("-", "_")
            if categories and type_dir not in categories and normalized_category not in categories:
                continue
            for json_file in self._iter_libchecker_rule_files(type_root):
                try:
                    payload = json.loads(json_file.read_text(encoding="utf-8"))
                    rel_file = json_file.relative_to(type_root)
                    signatures.append(self._build_libchecker_signature(type_dir, rel_file, payload))
                except Exception as e:
                    print(f"[WARN] Failed to load {json_file}: {e}")
        return signatures

    def load(self, categories: Optional[List[str]] = None) -> List[dict]:
        meta_path = self.sig_dir / "_meta.yaml"
        if meta_path.exists():
            try:
                text = meta_path.read_text(encoding="utf-8")
                meta = yaml.safe_load(text) if yaml else _parse_yaml_simple(text)
                self.db_version = meta.get("version", "unknown")
            except Exception:
                pass

        signatures: list[dict] = []
        seen_ids: set[str] = set()
        for sig in [*self._load_libchecker_signatures(categories), *self._load_yaml_signatures(categories)]:
            sig_id = str(sig.get("id") or "").strip()
            if not sig_id or sig_id in seen_ids:
                continue
            seen_ids.add(sig_id)
            signatures.append(sig)
        return signatures
