
import subprocess
import re
import sys
from . import config
from . import ui

def detect_prefixed_format(h):
    """Match hash against known prefix patterns. Returns (name, mode) or (None, None)."""
    for pattern, name, mode in config.PREFIXED_FORMATS:
        if re.match(pattern, h, re.IGNORECASE):
            return name, mode
    return None, None


def run_nth(h):
    """Run name-that-hash and extract (name, hashcat_mode) pairs.
    
    
    Example call of this function for:
        nth --text 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8 --no-banner

        5e884898da28...d8              ← the hash itself (no HC: → ignored, becomes nothing)
        Most Likely                     ← skip_words match → ignored
        SHA-256, HC: 1400 JtR: raw-sha256 Summary: 256-bit key...  ← HAS HC: → extract
        Keccak-256, HC: 17800          ← HAS HC: → extract
        Haval-128, JtR: haval-128-4   ← no HC: → becomes current_name
        Snefru-256, JtR: snefru-256   ← no HC: → becomes current_name
        Least Likely                    ← skip_words match → ignored
        RIPEMD-256, JtR: dynamic_140 Haval-256...  GOST R 34.11-94, HC: 6900 ...
    """
    try:
        r = subprocess.run(
            ["nth", "--text", h, "--no-banner"],
            capture_output=True, text=True, timeout=10,
        )
        results = []
        seen_modes = set()
        current_name = None

        for line in r.stdout.splitlines():
            line = line.strip()
            if not line:
                continue
            
            # HC as of hashcat, these lines show the hashcat number for the -m flag
            hc_match = re.search(r'HC:\s*(\d+)', line, re.IGNORECASE)
            if hc_match:
                mode = hc_match.group(1)
                if mode not in seen_modes:
                    seen_modes.add(mode)
                    name_part = re.sub(r',?\s*HC:\s*\d+.*', '', line).strip().lower()
                    name_part = re.sub(r',?\s*JtR:.*', '', name_part).strip()
                    name = name_part or current_name or "unknown"
                    results.append((name, mode))
            else:
                clean = re.sub(r'\s*[\[\(].*?[\]\)]', '', line).strip()
                skip_words = ('most likely', 'least likely', 'summary', 'partner')
                if clean and not any(s in clean.lower() for s in skip_words):
                    current_name = clean.lower()

        return results[:5]
    except Exception:
        return []

def run_hashid(h):
    '''
    Example for command:
    
    hashid 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8 
        Analyzing '5e884898da28...'       ← no [] → skipped
        [+] Snefru-256                    ← matches [+] → captures "+"  ... wait
        [+] SHA-256                       ← same
        [+] RIPEMD-256      
    '''
    try:
        r = subprocess.run(["hashid", h], capture_output=True, text=True, timeout=10)
        names = []
        for line in r.stdout.splitlines():
            # hashid format: "[+] SHA-256" — name follows the [+]
            m = re.match(r'\[\+\]\s*(.+)', line)
            if m:
                names.append(m.group(1).strip().lower())
        return names[:5]
    except Exception:
        return []

def name_to_mode(name):
    """Look up a hashcat mode number from a human-readable hash name."""
    name = name.lower().strip()
    if name in config.HASH_MODE_MAP:
        return config.HASH_MODE_MAP[name]
    # Substring match as fallback
    for key, mode in config.HASH_MODE_MAP.items():
        if key in name or name in key:
            return mode
    return None


def identify_hash(h):
    """
    Identify a hash string and return candidate list of [(name, mode)] tuples, and is_salted bool.

    First checks for known prefixes (salted formats). Then runs nth (name-that-hash) and/or hashid.
    The results of the 2 tools are then filtered to unique non-duplicate ones.

    """
    ui.section("Hash Identification")

    # Step 1: prefix detection
    name, mode = detect_prefixed_format(h)
    if name:
        if mode == 'john':
            ui.ok(f"Prefixed format: {name} (hashcat unsupported — will use John only)")
        else:
            ui.ok(f"Prefixed format: {name} → hashcat mode {mode}")
        return [(name, mode)], True

    # Step 2-3: tool-based identification
    nth_results    = run_nth(h)
    hashid_results = run_hashid(h)

    ui.info(f"nth     candidates: {nth_results[:4]}")
    ui.info(f"hashid  candidates: {hashid_results[:4]}")

    # Merge and deduplicate
    seen_modes = set()
    candidates = []

    for name, mode in nth_results:
        if mode not in seen_modes:
            seen_modes.add(mode)
            candidates.append((name, mode))
        if len(candidates) >= 4:
            break

    for name in hashid_results:
        mode = name_to_mode(name)
        if mode and mode not in seen_modes:
            seen_modes.add(mode)
            candidates.append((name, mode))
        if len(candidates) >= 4:
            break

    if not candidates:
        ui.warn("Could not map any identification to a known hashcat mode.")
        ui.warn("Run manually: hashcat --help | grep -i <name>")
        sys.exit(1)

    ui.ok(f"Will try candidates: {[(n, m) for n, m in candidates]}")
    return candidates, False
