import os
import shutil
import sys
import subprocess
import re
from . import config
from .ui import C, info, warn, section, err, hint, ok
from . import utils


def check_tools():
    """Return list of (tool, install_hint) for any missing CLI tools."""
    return [(t, cmd) for t, cmd in config.REQUIRED_TOOLS.items() if not shutil.which(t)]


def check_wordlists():
    """
    Scan all wordlist categories for available files.

    Returns (available, missing_critical, missing_recommended, missing_optional)
    where available = {name: path} and missing_* = [(name, hint), ...].
    """
    available, missing_critical, missing_recommended, missing_optional = {}, [], [], []

    for name, paths in config.WORDLIST_PATHS.items():
        found = utils._find_first_nonempty(paths)
        if found:
            available[name] = found
        else:
            install_hint = config.WORDLIST_INSTALL_HINTS.get(name, "")
            bucket = (
                missing_critical if name in config.CRITICAL_WORDLISTS else
                missing_recommended if name in config.RECOMMENDED_WORDLISTS else
                missing_optional
            )
            bucket.append((name, install_hint))

    return available, missing_critical, missing_recommended, missing_optional


def check_rules():
    """
    Scan rule files. Returns (available_rules, missing_builtin, missing_external).
    """
    available_rules, missing_builtin, missing_external = {}, [], []

    for name, path in config.RULE_PATHS.items():
        if os.path.exists(path):
            available_rules[name] = path
        else:
            install_hint = config.RULE_INSTALL_HINTS.get(name, "")
            bucket = missing_builtin if name in config.BUILTIN_RULES else missing_external
            bucket.append((name, install_hint))

    return available_rules, missing_builtin, missing_external


def run_dependencies():
    """
    Full dependency check. Returns dict of available wordlists.
    Exits on critical failures, warns on everything else.
    """
    print(f"""
{C.BOLD}{C.CYAN}
╔══════════════════════════════════════════╗
║          quinto — DEPENDENCIES           ║
╚══════════════════════════════════════════╝
{C.RESET}""")

    fatal = False

    # ── Tools ───────────────────────────────────────────────────────────
    section("Required Tools")
    missing_tools = check_tools()
    if missing_tools:
        fatal = True
        err("Missing required tools:\n")
        for tool, cmd in missing_tools:
            print(f"  {C.BOLD}{tool}{C.RESET}")
            for line in cmd.split('\n'):
                print(f"    {C.YELLOW}{line}{C.RESET}")
            print()
    else:
        ok("All required tools found (hashcat, john, nth, hashid)")
        check_hashcat_version()
        check_john_formats()

    # ── Wordlists ───────────────────────────────────────────────────────
    section("Wordlists")
    available_wl, missing_crit, missing_rec, missing_opt = check_wordlists()

    for name, path in available_wl.items():
        try:
            size_mb = os.path.getsize(path) / (1024 * 1024)
            ok(f"{name}: {path} ({size_mb:.0f} MB)")
        except Exception:
            ok(f"{name}: {path}")

    if missing_crit:
        fatal = True
        print()
        err("Missing CRITICAL wordlists (script cannot run without these):\n")
        for name, install_hint in missing_crit:
            print(f"  {C.RED}{C.BOLD}{name}{C.RESET}")
            for line in install_hint.split('\n'):
                print(f"    {C.YELLOW}{line}{C.RESET}")
            print()

    if missing_rec:
        print()
        warn("Missing RECOMMENDED wordlists (reduced crack coverage):\n")
        for name, install_hint in missing_rec:
            print(f"  {C.YELLOW}{C.BOLD}{name}{C.RESET}")
            for line in install_hint.split('\n'):
                print(f"    {C.YELLOW}{line}{C.RESET}")
            print()

    if missing_opt:
        print()
        info("Missing OPTIONAL wordlists:\n")
        for name, install_hint in missing_opt:
            print(f"  {C.DIM}{C.BOLD}{name}{C.RESET}")
            for line in install_hint.split('\n'):
                print(f"    {C.DIM}{line}{C.RESET}")
            print()

    # ── Rules ───────────────────────────────────────────────────────────
    section("Hashcat Rules")
    available_rules, missing_builtin, missing_external = check_rules()

    if available_rules:
        ok(f"Rules found: {list(available_rules.keys())}")

    if missing_builtin:
        warn("Missing BUILT-IN rules (should ship with hashcat):\n")
        for name, install_hint in missing_builtin:
            print(f"  {C.YELLOW}{C.BOLD}{name}{C.RESET} — {config.RULE_PATHS[name]}")
            hint(install_hint)
            print()

    if missing_external:
        info("Missing EXTERNAL rules (optional but recommended):\n")
        for name, install_hint in missing_external:
            print(f"  {C.DIM}{C.BOLD}{name}{C.RESET} — {config.RULE_PATHS[name]}")
            for line in install_hint.split('\n'):
                hint(line)
            print()

    # ── Verdict ─────────────────────────────────────────────────────────
    if fatal:
        print()
        err("Fix the critical issues above and re-run. Exiting.")
        sys.exit(1)

    # Create a starter config.toml on first run so users can see what's customisable
    if config.create_config_template():
        print()
        info(f"Created config template: {config.CONFIG_FILE}")
        hint("Edit it to override any path (all lines are commented out by default).")

    # Always show config location so users know where to find it
    section("Config Paths")
    info(f"Config file : {config.CONFIG_FILE}")
    info(f"Data dir    : {config.DATA_DIR}")
    info(f"State dir   : {config.STATE_DIR}")
    hint("Edit the config file to override any discovered path.")

    print()
    ok("dependencies passed.\n")
    return available_wl

def check_hashcat_version():
    """Print hashcat version and warn if outdated."""
    try:
        result = subprocess.run(["hashcat", "--version"], capture_output=True, text=True, timeout=10)
        version_str = result.stdout.strip()
        match = re.search(r'v?(\d+)\.(\d+)', version_str)
        if match and int(match.group(1)) < 6:
            warn(f"hashcat {version_str} is very old — upgrade to 6.x+")
            hint("Download from https://github.com/hashcat/hashcat/releases")
        else:
            ok(f"hashcat {version_str}")
    except Exception:
        pass


def check_john_formats():
    """Verify John is the Jumbo build (400+ formats)."""
    try:
        result = subprocess.run(
            ["john", "--list=formats"], capture_output=True, text=True, timeout=10
        )
        fmt_count = result.stdout.count(',') + 1 if result.stdout.strip() else 0
        if fmt_count < 100:
            warn(f"John reports only ~{fmt_count} formats — may not be Jumbo John")
            hint("Build from source: git clone https://github.com/openwall/john")
        else:
            ok(f"John the Ripper (Jumbo) — {fmt_count} formats available")
    except Exception:
        pass
