# 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_PROBABILITY = 0.35
LEET_CHAR_PROBABILITY = 0.5
NUMBER_SUFFIX_PROBABILITY = 0.30
SEPARATOR_PROBABILITY = 0.30
CAPITALIZE_PROBABILITY = 0.22
ALLCAPS_PROBABILITY = 0.08
ALLCAPS_MAX_LEN = 8
PAD_NUMBER_FLOOR = 10
PAD_NUMBER_CEIL = 9999

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",
]

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",
]

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",
]

HANDLE_PREFIXES = ["the", "mr", "dr", "lord", "captain", "uncle", "sir", "prof", "agent"]

NUMBER_SUFFIXES = ["42", "1337", "404", "64", "99", "007", "23", "88", "256", "512", "101", "777"]

SEPARATORS = ["_", "-"]


def _join(rng: random.Random, parts: list[str]) -> str:
    separator = rng.choice(SEPARATORS) if rng.random() < SEPARATOR_PROBABILITY else ""
    return separator.join(parts)


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 _build_core(rng: random.Random, interests: list[str]) -> str:
    seeds = _seed_tokens(interests)
    builders = [
        lambda: rng.choice(TECH_NOUNS),
        lambda: _join(rng, [rng.choice(HANDLE_ADJECTIVES), rng.choice(TECH_NOUNS)]),
        lambda: _join(rng, [rng.choice(TECH_NOUNS), rng.choice(HANDLE_CREATURES)]),
        lambda: _join(rng, [rng.choice(HANDLE_ADJECTIVES), rng.choice(HANDLE_CREATURES)]),
        lambda: _join(rng, [rng.choice(HANDLE_PREFIXES), rng.choice(TECH_NOUNS + HANDLE_CREATURES)]),
        lambda: _join(rng, [rng.choice(TECH_NOUNS), rng.choice(HANDLE_SUFFIX_WORDS)]),
    ]
    weights = [3, 4, 3, 2, 2, 2]
    if seeds:
        builders.append(
            lambda: _join(rng, [rng.choice(seeds), rng.choice(HANDLE_SUFFIX_WORDS + TECH_NOUNS)])
        )
        builders.append(lambda: _join(rng, [rng.choice(HANDLE_ADJECTIVES), rng.choice(seeds)]))
        weights += [5, 3]
    return rng.choices(builders, weights=weights)[0]()


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(text: str, rng: random.Random) -> 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
    core = _build_core(generator, interests or [])
    if generator.random() < LEET_PROBABILITY:
        core = apply_leet(core, generator)
    if generator.random() < NUMBER_SUFFIX_PROBABILITY:
        core = f"{core}{generator.choice(NUMBER_SUFFIXES)}"
    handle = sanitize_handle(core)
    if len(handle) < MIN_HANDLE_LEN:
        handle = f"{handle}{generator.randint(PAD_NUMBER_FLOOR, PAD_NUMBER_CEIL)}"
    handle = _apply_case(handle, generator)
    return handle or f"dev{generator.randint(PAD_NUMBER_FLOOR, PAD_NUMBER_CEIL)}"
