import os
import sys
from pathlib import Path
from platformdirs import user_config_dir, user_data_dir, user_state_dir
from .ui import C

# ── Platform-aware directories ───────────────────────────────────────────────
# These resolve to the correct path per OS automatically:
#   Linux:   ~/.config/quinto   ~/.local/share/quinto   ~/.local/state/quinto
#   macOS:   ~/Library/Preferences/quinto   ~/Library/Application Support/quinto
#   Windows: %APPDATA%\quinto   %LOCALAPPDATA%\quinto
CONFIG_DIR = Path(user_config_dir("quinto"))
DATA_DIR   = Path(user_data_dir("quinto"))
STATE_DIR  = Path(user_state_dir("quinto"))

def _ensure_app_dirs():
    """Create quinto's directories if they don't exist yet."""
    for d in (CONFIG_DIR, DATA_DIR, STATE_DIR):
        d.mkdir(parents=True, exist_ok=True)

# ── User config file ──────────────────────────────────────────────────────────
# Users can create this file to override any path below.
# Run 'quinto --deps' to auto-generate a starter template.
#   Linux:   ~/.config/quinto/config.toml
#   macOS:   ~/Library/Preferences/quinto/config.toml
#   Windows: %APPDATA%\quinto\config.toml
CONFIG_FILE = CONFIG_DIR / "config.toml"

def _load_user_config() -> dict:
    if not CONFIG_FILE.exists():
        return {}
    try:
        if sys.version_info >= (3, 11):
            import tomllib
        else:
            import tomli as tomllib
        with open(CONFIG_FILE, "rb") as f:
            return tomllib.load(f)
    except Exception as e:
        print(f"Warning: could not parse {CONFIG_FILE}: {e}", file=sys.stderr)
        return {}

_cfg = _load_user_config()

# ── Combinator mode ───────────────────────────────────────────────────────────
# Throughput penalty factor for combinator mode (-a 1).
# Measured: ~0.48x on SHA256. Adjust per your hardware.
COMBINATOR_SPEED_FACTOR = 0.5

# ── Mask directory ────────────────────────────────────────────────────────────
# Auto-discovers the hashcat masks directory across common install locations.
# Override in config.toml:  [paths]  mask_dir = "/your/path"
def _find_mask_dir() -> str:
    override = _cfg.get("paths", {}).get("mask_dir")
    if override:
        return override
    for candidate in [
        "/usr/share/hashcat/masks",         # Kali / Debian apt install
        "/opt/hashcat/masks",               # Manual GitHub release install
        "/usr/local/share/hashcat/masks",   # Some distros / macOS Homebrew
        str(Path.home() / "hashcat/masks"), # User-local manual install
    ]:
        if os.path.isdir(candidate):
            return candidate
    return "/usr/share/hashcat/masks"       # Placeholder if none found

MASK_DIR = _find_mask_dir()

# .hcmask files tried per patience tier (cumulative — each tier adds to previous)
MASK_FILES = {
    1: [  # quick
        "rockyou-1-60.hcmask",
        "8char-1l-1u-1d-1s-noncompliant.hcmask",
    ],
    2: [  # normal — runs tier 1 first, then these
        "rockyou-2-1800.hcmask",
        "rockyou-3-3600.hcmask",
    ],
    3: [  # patient — runs tiers 1+2 first, then these
        "rockyou-4-43200.hcmask",
        "rockyou-5-86400.hcmask",
        "8char-1l-1u-1d-1s-compliant.hcmask",
    ],
}

# Session file — stored in the state dir, not /tmp (survives reboots, correct per OS)
SESSION_FILE = str(STATE_DIR / "session.json")

# ── Wordlists ─────────────────────────────────────────────────────────────────
# Each key maps to a list of candidate paths tried in order — first existing
# non-empty file wins. This handles different distro and OS layouts.
#
# Users can prepend their own path in config.toml:
#   [wordlists]
#   rockyou = "/my/custom/rockyou.txt"
def _wl(key: str, *defaults: str) -> list:
    """Build a candidates list: user config override first, then OS defaults."""
    override = _cfg.get("wordlists", {}).get(key)
    candidates = [override] if override else []
    candidates.extend(defaults)
    return candidates

WORDLIST_PATHS = {
    "rockyou": _wl("rockyou",
        "/usr/share/wordlists/rockyou.txt",                     # Kali / Debian
        "/opt/homebrew/share/wordlists/rockyou.txt",            # macOS Homebrew
        str(Path.home() / "wordlists/rockyou.txt"),             # User-local
    ),
    "crackstation": _wl("crackstation",
        "/usr/share/wordlists/crackstation.txt",
        str(Path.home() / "wordlists/crackstation.txt"),
    ),
    "seclists_xato": _wl("seclists_xato",
        "/usr/share/seclists/Passwords/Common-Credentials/xato-net-10-million-passwords-1000000.txt",
        "/opt/homebrew/share/seclists/Passwords/Common-Credentials/xato-net-10-million-passwords-1000000.txt",
    ),
    "seclists_darkweb": _wl("seclists_darkweb",
        "/usr/share/seclists/Passwords/Common-Credentials/darkweb2017_top-10000.txt",
        "/opt/homebrew/share/seclists/Passwords/Common-Credentials/darkweb2017_top-10000.txt",
    ),
    "seclists_probable": _wl("seclists_probable",
        "/usr/share/seclists/Passwords/Common-Credentials/probable-v2-top12000.txt",
        "/usr/share/seclists/Passwords/Common-Credentials/probable-v2_top-12000.txt",   # alt filename
        "/opt/homebrew/share/seclists/Passwords/Common-Credentials/probable-v2-top12000.txt",
    ),
    "seclists_ncsc": _wl("seclists_ncsc",
        "/usr/share/seclists/Passwords/Common-Credentials/100k-most-used-passwords-NCSC.txt",
        "/opt/homebrew/share/seclists/Passwords/Common-Credentials/100k-most-used-passwords-NCSC.txt",
    ),
    "seclists_pwdb": _wl("seclists_pwdb",
        "/usr/share/seclists/Passwords/Common-Credentials/Pwdb_top-1000000.txt",
        "/opt/homebrew/share/seclists/Passwords/Common-Credentials/Pwdb_top-1000000.txt",
    ),
}

# Criticality tiers — controls dependency check behavior
CRITICAL_WORDLISTS    = {"rockyou"}
RECOMMENDED_WORDLISTS = {"seclists_darkweb", "seclists_probable", "seclists_xato"}
OPTIONAL_WORDLISTS    = {"crackstation", "seclists_ncsc", "seclists_pwdb"}

WORDLIST_INSTALL_HINTS = {
    "rockyou": (
        "sudo mkdir -p /usr/share/wordlists && sudo wget -q\n"
        "    https://github.com/brannondorsey/naive-hashcat/releases/download/data/rockyou.txt\n"
        "    -O /usr/share/wordlists/rockyou.txt"
    ),
    "crackstation": (
        "# Download from https://crackstation.net/crackstation-wordlist-password-cracking-dictionary.htm\n"
        "    # ~15GB uncompressed — place at /usr/share/wordlists/crackstation.txt"
    ),
    "seclists_xato":    "sudo git clone --depth 1 https://github.com/danielmiessler/SecLists /usr/share/seclists",
    "seclists_darkweb": "sudo git clone --depth 1 https://github.com/danielmiessler/SecLists /usr/share/seclists",
    "seclists_probable":"sudo git clone --depth 1 https://github.com/danielmiessler/SecLists /usr/share/seclists",
    "seclists_ncsc":    "sudo git clone --depth 1 https://github.com/danielmiessler/SecLists /usr/share/seclists",
    "seclists_pwdb":    "sudo git clone --depth 1 https://github.com/danielmiessler/SecLists /usr/share/seclists",
}

# ── Passphrase attack config ──────────────────────────────────────────────────
PASSPHRASE_DICT_PATHS = _wl("eff_diceware",
    "/usr/share/wordlists/eff-diceware.txt",
    "/usr/share/wordlists/eff_large_wordlist.txt",
    "/opt/homebrew/share/wordlists/eff-diceware.txt",        # macOS Homebrew
    str(Path.home() / "wordlists/eff-diceware.txt"),         # User-local
)
PASSPHRASE_GEN_DIR  = str(DATA_DIR / "passphrase-gen")       # was ~/.quinto/passphrase-gen
PASSPHRASE_SEPARATORS = ["-", "_", ""]

def sep_to_name(sep):
    return {"-": "dash", "_": "under", "": "nosep"}.get(sep, sep.strip() or "nosep")

# ── Rules ─────────────────────────────────────────────────────────────────────
# Auto-discovers rule files across common install locations.
# Override in config.toml:  [rules]  best64 = "/your/path/best64.rule"
def _find_rule(key: str, *candidates: str) -> str:
    """Return the first existing rule file, or the first candidate as a placeholder."""
    override = _cfg.get("rules", {}).get(key)
    if override and os.path.isfile(override):
        return override
    for c in candidates:
        if c and os.path.isfile(c):
            return c
    return candidates[0] if candidates else ""

RULE_PATHS = {
    "best64":    _find_rule("best64",
        "/usr/share/hashcat/rules/best64.rule",
        "/opt/hashcat/rules/best64.rule",
        "/usr/local/share/hashcat/rules/best64.rule",
    ),
    "onetorule": _find_rule("onetorule",
        "/usr/share/hashcat/rules/OneRuleToRuleThemAll.rule",
        "/opt/hashcat/rules/OneRuleToRuleThemAll.rule",
    ),
    "toggles":   _find_rule("toggles",
        "/usr/share/hashcat/rules/toggles1.rule",
        "/opt/hashcat/rules/toggles1.rule",
        "/usr/local/share/hashcat/rules/toggles1.rule",
    ),
    "dive":      _find_rule("dive",
        "/usr/share/hashcat/rules/dive.rule",
        "/opt/hashcat/rules/dive.rule",
        "/usr/local/share/hashcat/rules/dive.rule",
    ),
}
BUILTIN_RULES  = {"best64", "toggles", "dive"}
EXTERNAL_RULES = {"onetorule"}

RULE_INSTALL_HINTS = {
    "best64":  "Should ship with hashcat 7.x — check /opt/hashcat/rules/ or /usr/share/hashcat/rules/",
    "toggles": "Should ship with hashcat — check /opt/hashcat/rules/ or /usr/share/hashcat/rules/",
    "dive":    "Should ship with hashcat — check /opt/hashcat/rules/ or /usr/share/hashcat/rules/",
    "onetorule": (
        "sudo wget -q https://raw.githubusercontent.com/NotSoSecure/password_cracking_rules/"
        "master/OneRuleToRuleThemAll.rule \\\n"
        "       -O /usr/share/hashcat/rules/OneRuleToRuleThemAll.rule"
    ),
}

# ── Config template ───────────────────────────────────────────────────────────
def create_config_template() -> bool:
    """
    Write a starter config.toml to CONFIG_DIR if one doesn't exist yet.
    Called by 'quinto --deps' so users get a ready-to-edit file on first run.
    Returns True if a new file was created, False if one already existed.
    """
    _ensure_app_dirs()
    if CONFIG_FILE.exists():
        return False

    lines = [
        "# quinto configuration\n",
        f"# Location: {CONFIG_FILE}\n",
        "#\n",
        "# All values are optional — quinto auto-discovers paths if this file is empty.\n",
        "# Uncomment and edit any line to override the discovered default.\n\n",
        "# [wordlists]\n",
    ]
    for name, paths in WORDLIST_PATHS.items():
        first = next((p for p in paths if p), "")
        lines.append(f"# {name} = \"{first}\"\n")
    lines.append("\n# [rules]\n")
    for name, path in RULE_PATHS.items():
        lines.append(f"# {name} = \"{path}\"\n")
    lines.append(f"\n# [paths]\n# mask_dir = \"{MASK_DIR}\"\n")

    CONFIG_FILE.write_text("".join(lines))
    return True

# ── Hash mode mapping ─────────────────────────────────────────────────────────
# Maps human-readable names (from nth/hashid output) to hashcat -m values
HASH_MODE_MAP = {
    "md5":                  "0",
    "md4":                  "900",
    "md2":                  "70",
    "sha1":                 "100",
    "sha-1":                "100",
    "sha224":               "1300",
    "sha-224":              "1300",
    "sha256":               "1400",
    "sha-256":              "1400",
    "sha384":               "10800",
    "sha-384":              "10800",
    "sha512":               "1700",
    "sha-512":              "1700",
    "sha3-256":             "17300",
    "sha3-512":             "17500",
    "ntlm":                 "1000",
    "net-ntlmv1":           "5500",
    "net-ntlmv2":           "5600",
    "netlmv2":              "5600",
    "bcrypt":               "3200",
    "blowfish":             "3200",
    "md5crypt":             "500",
    "md5(unix)":            "500",
    "sha256crypt":          "7400",
    "sha512crypt":          "1800",
    "sha512(unix)":         "1800",
    "yescrypt":             "161",
    "scrypt":               "8900",
    "argon2i":              "argon2",
    "argon2d":              "argon2",
    "argon2id":             "argon2",
    "mysql323":             "200",
    "mysql4.1":             "300",
    "mysql5":               "300",
    "whirlpool":            "6100",
    "ripemd160":            "6000",
    "ripemd-160":           "6000",
    "blake2":               "600",
    "crc32":                "11500",
    "lm":                   "3000",
    "des":                  "1500",
    "adler32":              "11500",
    "haval":                "18700",
    "tiger":                "20",
    "snefru":               "14100",
    "phpass":               "400",
    "drupal":               "7900",
    "django(sha1)":         "124",
    "django(sha256)":       "10000",
    "pbkdf2-sha256":        "10900",
    "pbkdf2-sha512":        "12100",
    "pbkdf2-hmac-sha1":     "160",
    "pbkdf2-hmac-sha256":   "10900",
    "pbkdf2-hmac-sha512":   "12100",
    "lastpass":             "6800",
    "1password":            "8200",
    "keepass":              "13400",
    "veracrypt":            "13711",
    "android fde":          "8800",
    "luks":                 "14600",
    "wpa/wpa2":             "22000",
    "wpa-pmkid":            "22001",
}

# Maps file extensions to the john *2john extraction tool
FILE_TO_JOHN = {
    ".zip":  "zip2john",
    ".rar":  "rar2john",
    ".7z":   "7z2john",
    ".pdf":  "pdf2john",
    ".kdbx": "keepass2john",
    ".doc":  "office2john",
    ".docx": "office2john",
    ".xls":  "office2john",
    ".xlsx": "office2john",
    ".ppt":  "office2john",
    ".pptx": "office2john",
}

# Hash modes where the algorithm is intentionally slow (key-stretching).
# These get a reduced attack plan — fewer rule combos, no brute-force masks.
SLOW_MODES = {"3200", "161", "23900", "8900", "9300", "9200", "400", "10000", "10900", "12100"}

# Hash modes fast enough for mask brute-force to be practical
FAST_MODES = {"0", "100", "1000", "1400", "1700", "900"}

REQUIRED_TOOLS = {
    "hashcat": (
        "# Option A (recommended): Install from GitHub\n"
        "    wget https://github.com/hashcat/hashcat/releases/download/v7.1.2/hashcat-7.1.2.7z\n"
        "    7z x hashcat-7.1.2.7z && sudo mv hashcat-7.1.2 /opt/hashcat\n"
        "    sudo ln -sf /opt/hashcat/hashcat.bin /usr/local/bin/hashcat\n"
        "    # Option B: sudo apt install hashcat  (may be outdated)"
    ),
    "john": (
        "# Build Jumbo John from source:\n"
        "    sudo apt install -y build-essential libssl-dev zlib1g-dev yasm libgmp-dev libpcap-dev\n"
        "    git clone --depth 1 https://github.com/openwall/john.git ~/john-build\n"
        "    cd ~/john-build/src && ./configure && make -sj$(nproc) && cd ~\n"
        "    # Create wrapper scripts (symlinks break John's config lookup):\n"
        "    for f in ~/john-build/run/john ~/john-build/run/*2john*; do\n"
        '        name=$(basename "$f")\n'
        '        printf \'#!/bin/sh\\nexec "%s" "$@"\\n\' "$f" | sudo tee "/usr/local/bin/$name" > /dev/null\n'
        '        sudo chmod +x "/usr/local/bin/$name"\n'
        "    done"
    ),
    "nth":    "pip install name-that-hash --break-system-packages",
    "hashid": "pip install hashid --break-system-packages",
}

# Regex patterns for hashes that have distinctive prefixes (salted formats).
# These can be identified with 100% confidence without nth/hashid.
PREFIXED_FORMATS = [
    (r'^\$y\$',           'yescrypt',              '161'),
    (r'^\$gy\$',          'gost-yescrypt',         '23900'),
    (r'^\$2id\$',         'bcrypt',                '3200'),
    (r'^\$2[abxy]\$',     'bcrypt',                '3200'),
    (r'^\$6\$',           'sha512crypt',           '1800'),
    (r'^\$5\$',           'sha256crypt',           '7400'),
    (r'^\$1\$',           'md5crypt',              '500'),
    (r'^\$apr1\$',        'md5crypt-apache',       '1600'),
    (r'^\$md5,',          'md5crypt-sun',          '3300'),
    (r'^\$7\$',           'scrypt',                '8900'),
    (r'^\$argon2id\$',    'argon2id (john only)',   'john'),
    (r'^\$argon2i\$',     'argon2i (john only)',    'john'),
    (r'^\$argon2d\$',     'argon2d (john only)',    'john'),
    (r'^\$P\$',           'phpass (WordPress)',     '400'),
    (r'^\$H\$',           'phpass (phpBB)',         '400'),
    (r'^pbkdf2_sha256\$', 'django-pbkdf2-sha256',  '10000'),
    (r'^pbkdf2_sha1\$',   'django-pbkdf2-sha1',    '10000'),
    (r'^bcrypt\$',        'django-bcrypt',          '3200'),
    (r'^\$9\$',           'cisco-type9 (scrypt)',   '9300'),
    (r'^\$8\$',           'cisco-type8 (pbkdf2)',   '9200'),
    (r'^aad3b435b51404ee','lm-hash (empty)',        '3000'),
]


BANNER = f"""
{C.CYAN}{C.BOLD}
     ██████╗ ██╗   ██╗██╗███╗   ██╗████████╗ ██████╗
    ██╔═══██╗██║   ██║██║████╗  ██║╚══██╔══╝██╔═══██╗
    ██║   ██║██║   ██║██║██╔██╗ ██║   ██║   ██║   ██║
    ██║▄▄ ██║██║   ██║██║██║╚██╗██║   ██║   ██║   ██║
    ╚██████╔╝╚██████╔╝██║██║ ╚████║   ██║   ╚██████╔╝
     ╚══▀▀═╝  ╚═════╝ ╚═╝╚═╝  ╚═══╝   ╚═╝    ╚═════╝
{C.RESET}{C.DIM}    I'm not a thief.  I'm a locksmith. — Quinto, master safecracker{C.RESET}
"""

# Create quinto's directories on first import (no-op if they already exist)
_ensure_app_dirs()
