# retoor <retoor@molodetz.nl>

import random
import re
from typing import Optional

from devplacepy.services.bot.config import HANDLE_SUFFIX_WORDS

MIN_HANDLE_LEN = 3
MAX_HANDLE_LEN = 20
MIN_SEED_TOKEN_LEN = 3
LEET_CHAR_PROBABILITY = 0.35
NUMBER_SUFFIX_PROBABILITY = 0.26
DECORATION_STYLES = ["plain", "leet", "vowel"]
DECORATION_WEIGHTS = [64, 22, 14]
CAPITALIZE_PROBABILITY = 0.20
ALLCAPS_PROBABILITY = 0.06
ALLCAPS_MAX_LEN = 8
PAD_NUMBER_FLOOR = 10
PAD_NUMBER_CEIL = 9999
VOWELS = "aeiou"

LEET_MAP = {
    "a": "4",
    "e": "3",
    "i": "1",
    "o": "0",
    "s": "5",
    "t": "7",
    "l": "1",
    "g": "9",
    "b": "8",
}

TECH_NOUNS = [
    "byte", "bit", "nibble", "null", "void", "root", "kernel", "daemon",
    "socket", "buffer", "cache", "stack", "heap", "pointer", "segfault",
    "regex", "lambda", "vector", "packet", "proxy", "token", "hash", "cipher",
    "vortex", "flux", "glitch", "pixel", "neon", "cyber", "quantum", "thread",
    "mutex", "syntax", "binary", "octet", "raster", "shader", "codec",
    "vertex", "matrix", "scalar", "payload", "opcode", "runtime", "compiler",
    "parser", "lexer", "sandbox", "firmware", "bytecode", "checksum",
    "entropy", "signal", "overflow", "nibbler", "cursor", "patch", "semaphore",
    "spinlock", "deadlock", "webhook", "endpoint", "gateway", "registry",
    "pipeline", "tensor", "gradient", "cluster", "shard", "replica", "schema",
    "commit", "branch", "rebase", "stash", "blob", "symlink", "inode", "mmap",
    "syscall", "interrupt", "watchdog", "heartbeat", "beacon", "subnet",
    "datagram", "loopback", "nullref", "dangle", "leak", "trace", "kilobyte",
    "qubit", "monad", "closure", "thunk", "fiber", "coro", "atom",
]

HANDLE_ADJECTIVES = [
    "dark", "mad", "lazy", "quantum", "cyber", "neon", "ghost", "hyper",
    "ultra", "neo", "retro", "raw", "zero", "async", "blind", "silent",
    "atomic", "fatal", "rogue", "idle", "stale", "dirty", "lucid", "prime",
    "hex", "cold", "lone", "wired", "broken", "feral", "stray", "numb",
    "vivid", "molten", "frozen", "hollow", "jagged", "static", "volatile",
    "nomadic", "unsigned", "verbose", "terse", "nocturnal", "caffeinated",
    "deprecated", "headless", "stateless", "immutable", "recursive",
]

HANDLE_CREATURES = [
    "fox", "wolf", "raven", "owl", "octopus", "gecko", "mantis", "viper",
    "lynx", "moth", "hydra", "kraken", "phoenix", "dragon", "falcon",
    "panther", "cobra", "badger", "otter", "sloth", "hornet", "beetle",
    "lemur", "narwhal", "basilisk", "magpie", "heron", "ferret", "weasel",
    "jackal", "marten", "axolotl", "tardigrade", "wyvern", "griffin",
    "salamander", "newt", "stoat", "crow", "shrike", "ibis",
]

HANDLE_PREFIXES = [
    "the", "mr", "dr", "lord", "captain", "uncle", "sir", "prof", "agent",
    "old", "lil", "big", "saint", "baron", "comrade", "major",
]

ROLE_WORDS = [
    "dev", "coder", "hacker", "ninja", "guru", "wizard", "smith", "monk",
    "nomad", "ghost", "pilot", "surgeon", "gremlin", "goblin", "wrangler",
    "jockey", "slinger", "herder", "whisperer", "monkey", "sage", "druid",
    "ronin", "samurai", "pirate", "tinkerer", "alchemist", "janitor",
    "plumber", "gardener", "scribe", "warden", "ranger", "miner", "forge",
]

NUMBER_SUFFIXES = [
    "42", "1337", "404", "64", "99", "007", "23", "88", "256", "512", "101",
    "777", "13", "21", "69", "420", "127", "255", "80", "443", "3000", "8080",
    "2k", "9000", "65535", "16", "32", "128",
]

VERSION_TAGS = [
    "v2", "v3", "x64", "x86", "js", "py", "sh", "io", "fn", "db", "ai", "os",
    "hq", "exe", "dev", "rs", "go", "cpp", "ml",
]

JOIN_STYLES = ["none", "under", "hyphen", "camel"]
JOIN_WEIGHTS = [46, 19, 12, 23]


def _seed_tokens(interests: list[str]) -> list[str]:
    tokens: list[str] = []
    for term in interests:
        for piece in re.split(r"[^a-z0-9]+", term.lower()):
            if len(piece) >= MIN_SEED_TOKEN_LEN:
                tokens.append(piece)
    return tokens


def _seed_word(rng: random.Random, seeds: list[str]) -> str:
    return rng.choice(seeds) if seeds else rng.choice(TECH_NOUNS)


def _tokens(rng: random.Random, seeds: list[str]) -> list[str]:
    noun = lambda: rng.choice(TECH_NOUNS)
    adjective = lambda: rng.choice(HANDLE_ADJECTIVES)
    creature = lambda: rng.choice(HANDLE_CREATURES)
    role = lambda: rng.choice(ROLE_WORDS)
    prefix = lambda: rng.choice(HANDLE_PREFIXES)
    suffix = lambda: rng.choice(HANDLE_SUFFIX_WORDS)
    seed = lambda: _seed_word(rng, seeds)
    plans = [
        (7, lambda: [noun()]),
        (3, lambda: [role()]),
        (8, lambda: [adjective(), noun()]),
        (6, lambda: [noun(), creature()]),
        (5, lambda: [adjective(), creature()]),
        (5, lambda: [noun(), role()]),
        (4, lambda: [adjective(), role()]),
        (4, lambda: [creature(), role()]),
        (4, lambda: [prefix(), noun()]),
        (3, lambda: [prefix(), creature()]),
        (4, lambda: [role(), noun()]),
        (3, lambda: [noun(), suffix()]),
        (3, lambda: [adjective(), suffix()]),
        (3, lambda: [noun(), noun()]),
        (2, lambda: [adjective(), noun(), creature()]),
        (2, lambda: [prefix(), adjective(), noun()]),
        (6, lambda: [seed(), role()]),
        (5, lambda: [adjective(), seed()]),
        (4, lambda: [seed(), noun()]),
        (3, lambda: [seed()]),
    ]
    weights = [weight for weight, _ in plans]
    builders = [builder for _, builder in plans]
    return rng.choices(builders, weights=weights)[0]()


def _dedupe(rng: random.Random, tokens: list[str]) -> list[str]:
    result: list[str] = []
    for token in tokens:
        if result and result[-1] == token:
            token = rng.choice(TECH_NOUNS)
        result.append(token)
    return result


def _fuse(rng: random.Random, tokens: list[str]) -> str:
    if len(tokens) == 1:
        return tokens[0]
    style = rng.choices(JOIN_STYLES, weights=JOIN_WEIGHTS)[0]
    if style == "camel":
        head, *rest = tokens
        return head + "".join(part[:1].upper() + part[1:] for part in rest)
    separator = {"none": "", "under": "_", "hyphen": "-"}[style]
    return separator.join(tokens)


def _drop_vowels(rng: random.Random, text: str) -> str:
    kept = [text[0]]
    for char in text[1:]:
        if char in VOWELS and rng.random() < 0.55:
            continue
        kept.append(char)
    result = "".join(kept)
    return result if len(result) >= MIN_HANDLE_LEN else text


def _maybe_tag(rng: random.Random, core: str) -> str:
    if rng.random() >= NUMBER_SUFFIX_PROBABILITY:
        return core
    return f"{core}{rng.choice(NUMBER_SUFFIXES + VERSION_TAGS)}"


def apply_leet(text: str, rng: random.Random) -> str:
    chars: list[str] = []
    for char in text:
        replacement = LEET_MAP.get(char.lower())
        if replacement and rng.random() < LEET_CHAR_PROBABILITY:
            chars.append(replacement)
        else:
            chars.append(char)
    return "".join(chars)


def _apply_case(rng: random.Random, text: str) -> str:
    roll = rng.random()
    if roll < ALLCAPS_PROBABILITY and len(text) <= ALLCAPS_MAX_LEN:
        return text.upper()
    if roll < ALLCAPS_PROBABILITY + CAPITALIZE_PROBABILITY:
        return text[:1].upper() + text[1:]
    return text


def sanitize_handle(raw: str) -> str:
    handle = re.sub(r"[^a-zA-Z0-9_-]", "", str(raw).strip())
    handle = re.sub(r"[_-]{2,}", lambda match: match.group(0)[0], handle).strip("_-")
    return handle[:MAX_HANDLE_LEN].rstrip("_-")


def make_handle(interests: Optional[list[str]] = None, rng: Optional[random.Random] = None) -> str:
    generator = rng or random
    seeds = _seed_tokens(interests or [])
    core = _fuse(generator, _dedupe(generator, _tokens(generator, seeds)))
    decoration = generator.choices(DECORATION_STYLES, weights=DECORATION_WEIGHTS)[0]
    if decoration == "vowel" and core.isalpha() and core.islower() and len(core) > 6:
        core = _drop_vowels(generator, core)
    elif decoration == "leet":
        core = apply_leet(core, generator)
    if decoration != "leet":
        core = _maybe_tag(generator, core)
    handle = sanitize_handle(core)
    if len(handle) < MIN_HANDLE_LEN:
        handle = f"{handle}{generator.randint(PAD_NUMBER_FLOOR, PAD_NUMBER_CEIL)}"
    handle = _apply_case(generator, handle)
    return handle or f"dev{generator.randint(PAD_NUMBER_FLOOR, PAD_NUMBER_CEIL)}"
