#!/usr/bin/env python3
"""
Central place to maintain log patterns for the AP+MD workflow.

Source of truth: `skill_log_collector/SKILL.md` section "log pattern".
Keep this module low-dependency and pure (no filesystem scanning here).
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Iterable, Tuple


@dataclass(frozen=True)
class LogPatterns:
    # aplog
    aplog_dir_suffixes: Tuple[str, ...]
    aplog_file_prefixes: Tuple[str, ...]
    aplog_logcat_files: Tuple[str, ...]
    aplog_bugreport_prefix: str
    aplog_bugreport_zip_token: str
    aplog_inplace_dirnames: Tuple[str, ...]
    aplog_logs_dirname: str
    aplog_bugreport_extracted_dirname: str
    filter_dir_suffix: str
    aplog_merged_default_name: str
    aplog_merged_prefixes: Tuple[str, ...]

    # mdlog
    mdlog_dir_suffixes: Tuple[str, ...]
    mdlog_primary_globs: Tuple[str, ...]
    mdlog_extra_dir_globs: Tuple[str, ...]

    # netlog
    netlog_dir_suffixes: Tuple[str, ...]
    netlog_file_globs: Tuple[str, ...]

    # crashlog
    crashlog_dir_tokens: Tuple[str, ...]


PATTERNS = LogPatterns(
    # From `skill_log_collector/SKILL.md`:
    # - diag_logs/logs
    # - bugreport*.zip/FS/data/misc/logs/
    aplog_dir_suffixes=("diag_logs/logs", "FS/data/misc/logs"),
    aplog_file_prefixes=("aplog", "klog"),
    aplog_logcat_files=(
        "logsdk/logcat_main.txt",
        "logsdk/logcat_system.txt",
        "logsdk/logcat_radio.txt",
        "logsdk/logcat_crash.txt",
        "logsdk/logcat_kernel.txt",
    ),
    aplog_bugreport_prefix="bugreport-",
    aplog_bugreport_zip_token="bugreport",
    aplog_inplace_dirnames=("logs", "bugreport_extracted"),
    aplog_logs_dirname="logs",
    aplog_bugreport_extracted_dirname="bugreport_extracted",
    filter_dir_suffix="_filter",
    aplog_merged_default_name="merged_all_aplog.txt",
    aplog_merged_prefixes=("merged_logs_ordered_", "merged_all_aplog"),

    # From `skill_log_collector/SKILL.md`:
    # - diag_logs/modem/
    mdlog_dir_suffixes=("diag_logs/modem",),
    mdlog_primary_globs=("*.qmdl", "*.qmdl2", "*.qdss", "*.hdf", "*.isf"),
    mdlog_extra_dir_globs=("**/modemlogs-android_logs_*",),

    # From `skill_log_collector/SKILL.md`:
    # - _pcap
    netlog_dir_suffixes=("_pcap",),
    netlog_file_globs=(
        "*_pcap*",
    ),

    # From `skill_log_collector/SKILL.md`:
    # - ramdump/
    crashlog_dir_tokens=("ramdump",),
)


def _path_str(path: Path) -> str:
    # Use posix path to make suffix checks stable across environments.
    return path.as_posix()


def looks_like_aplog_dir(path: Path) -> bool:
    p = _path_str(path)
    return any(p.endswith(suf) for suf in PATTERNS.aplog_dir_suffixes)


def is_diag_logs_logs_dir(path: Path) -> bool:
    return _path_str(path).endswith("diag_logs/logs")


def has_prefixed_files(filenames: Iterable[str], prefixes: Iterable[str]) -> bool:
    prefixes = tuple(p.lower() for p in prefixes)
    for fn in filenames:
        low = fn.lower()
        if any(low.startswith(p) for p in prefixes):
            return True
    return False


def has_aplog_fragments(filenames: Iterable[str]) -> bool:
    # Used for detecting directories containing aplog/klog fragments.
    return has_prefixed_files(filenames, PATTERNS.aplog_file_prefixes)


def is_bugreport_txt_name(name: str) -> bool:
    low = name.lower()
    return low.startswith(PATTERNS.aplog_bugreport_prefix) and low.endswith(".txt")


def is_bugreport_zip_name(name: str) -> bool:
    low = name.lower()
    return low.endswith(".zip") and (PATTERNS.aplog_bugreport_zip_token in low)


def aplog_inplace_dirnames() -> Tuple[str, ...]:
    return PATTERNS.aplog_inplace_dirnames


def is_filter_dir_name(name: str) -> bool:
    return name.endswith(PATTERNS.filter_dir_suffix)


def is_logcat_basename(name: str) -> bool:
    basenames = {Path(p).name for p in PATTERNS.aplog_logcat_files}
    if name in basenames:
        return True
    low = name.lower()
    return low == "logcat.txt" or (low.startswith("logcat") and low.endswith(".txt"))


def is_ap_text_name(name: str) -> bool:
    low = name.lower()
    if is_bugreport_txt_name(name) or is_logcat_basename(name):
        return True
    if not low.endswith(".txt"):
        return False
    return any(low.startswith(prefix.lower()) for prefix in PATTERNS.aplog_merged_prefixes)


def looks_like_mdlog_dir(path: Path) -> bool:
    p = _path_str(path)
    return any(p.endswith(suf) for suf in PATTERNS.mdlog_dir_suffixes)


def looks_like_net_log(path: Path) -> bool:
    name_low = path.name.lower()
    if path.is_dir():
        return any(name_low.endswith(suf) for suf in PATTERNS.netlog_dir_suffixes)
    for pat in PATTERNS.netlog_file_globs:
        if path.match(pat):
            return True
    return False


def looks_like_crash_log(path: Path) -> bool:
    # Directory-style detection, because crash logs usually come as folders.
    name_low = path.name.lower()
    if path.is_dir():
        return any(tok in name_low for tok in PATTERNS.crashlog_dir_tokens)
    # If a file lives under a ramdump folder, treat it as crash-related.
    try:
        return any(tok in parent.name.lower() for parent in path.parents for tok in PATTERNS.crashlog_dir_tokens)
    except Exception:
        return False


def iter_mdlog_primary_dirs(root: Path) -> Iterable[Path]:
    """Yield parent dirs that contain mdlog primary files under root."""
    seen = set()
    for pat in PATTERNS.mdlog_primary_globs:
        for f in root.rglob(pat):
            try:
                d = f.parent.resolve()
            except Exception:
                continue
            if d in seen:
                continue
            seen.add(d)
            yield Path(d)
