"""
One-time script to download and merge nickname sources into a single mapping.

Sources:
- carltonnorthern/nicknames (MIT) — names.csv
- onyxrev/common_nickname_csv (Public domain) — nicknames.csv
- HaJongler/diminutives.db (Public domain) — male_diminutives.csv, female_diminutives.csv

Strategy: For each source, read (canonical, nickname) pairs directly. Each nickname
maps to the longest canonical form seen across all sources. No union-find — transitive
chaining causes mega-groups where everything merges together.
"""

import csv
import io
import json
import logging
import urllib.request
from pathlib import Path

logging.basicConfig(level=logging.INFO, format="%(message)s")
log = logging.getLogger(__name__)


def fetch(url: str) -> str:
    log.info(f"Downloading {url}")
    with urllib.request.urlopen(url) as resp:
        return resp.read().decode("utf-8")


def load_carltonnorthern() -> list[tuple[str, str]]:
    """carltonnorthern/nicknames — names.csv: name1,relationship,name2 format.
    relationship is 'has_nickname' (name1 is canonical, name2 is nickname)."""
    url = "https://raw.githubusercontent.com/carltonnorthern/nicknames/master/names.csv"
    text = fetch(url)
    pairs: list[tuple[str, str]] = []
    reader = csv.reader(io.StringIO(text))
    for row in reader:
        if len(row) < 3:
            continue
        name1 = row[0].strip().lower()
        relationship = row[1].strip().lower()
        name2 = row[2].strip().lower()
        if relationship != "has_nickname":
            continue
        if name1 and name2 and name1 != name2 and name1.isalpha() and name2.isalpha():
            pairs.append((name1, name2))
    log.info(f"  carltonnorthern: {len(pairs)} pairs")
    return pairs


def load_onyxrev() -> list[tuple[str, str]]:
    """onyxrev/common_nickname_csv — nicknames.csv: id,name,nickname format."""
    url = "https://raw.githubusercontent.com/onyxrev/common_nickname_csv/master/nicknames.csv"
    text = fetch(url)
    pairs: list[tuple[str, str]] = []
    reader = csv.reader(io.StringIO(text))
    next(reader, None)  # skip header
    for row in reader:
        if len(row) < 3:
            continue
        canonical = row[1].strip().lower()
        nick = row[2].strip().lower()
        if nick and canonical and nick != canonical and nick.isalpha() and canonical.isalpha():
            pairs.append((canonical, nick))
    log.info(f"  onyxrev: {len(pairs)} pairs")
    return pairs


def load_diminutives() -> list[tuple[str, str]]:
    """HaJongler/diminutives.db — male/female_diminutives.csv: canonical,nick1,nick2,..."""
    pairs: list[tuple[str, str]] = []
    for gender in ("male", "female"):
        url = f"https://raw.githubusercontent.com/HaJongler/diminutives.db/master/{gender}_diminutives.csv"
        text = fetch(url)
        reader = csv.reader(io.StringIO(text))
        for row in reader:
            if len(row) < 2:
                continue
            canonical = row[0].strip().lower()
            for nick in row[1:]:
                nick = nick.strip().lower()
                if nick and canonical and nick != canonical and nick.isalpha() and canonical.isalpha():
                    pairs.append((canonical, nick))
    log.info(f"  diminutives.db: {len(pairs)} pairs")
    return pairs


def build_nickname_mapping(all_pairs: list[tuple[str, str]]) -> dict[str, str]:
    """Build nickname → canonical mapping.

    For each nickname, collect all canonical names it maps to across sources,
    then pick the one that appears most frequently. Ties broken by length (longer wins).
    Only keep mappings where canonical is strictly longer than nickname.
    """
    from collections import Counter

    # Count how many nicknames each canonical name has (popularity measure)
    canonical_popularity: Counter[str] = Counter()
    for canonical, _ in all_pairs:
        canonical_popularity[canonical] += 1

    # For each nickname, count how many sources/pairs point to each canonical
    nick_to_canonicals: dict[str, Counter[str]] = {}
    for canonical, nick in all_pairs:
        if canonical == nick:
            continue
        nick_to_canonicals.setdefault(nick, Counter())[canonical] += 1

    mapping: dict[str, str] = {}
    for nick, canonical_counts in nick_to_canonicals.items():
        # Pick the most frequent canonical; break ties by overall popularity, then length
        best = max(
            canonical_counts,
            key=lambda c: (canonical_counts[c], canonical_popularity[c], len(c)),
        )
        if len(best) > len(nick):
            mapping[nick] = best

    # Remove if the canonical itself maps to something (prevent chains)
    final = {}
    for nick, canonical in mapping.items():
        if canonical not in mapping:
            final[nick] = canonical

    # Curated overrides: force correct mappings for common nicknames that
    # get wrong results from automated merging, and remove false positives
    # where formal names are incorrectly treated as nicknames
    FORCE_MAPPINGS = {
        "bill": "william", "billy": "william", "willy": "william",
        "mike": "michael", "mikey": "michael", "mick": "michael",
        "joe": "joseph", "joey": "joseph",
        "will": "william",
        "beth": "elizabeth", "betty": "elizabeth", "betsy": "elizabeth",
        "bob": "robert", "bobby": "robert", "robby": "robert",
        "rob": "robert", "robbie": "robert",
        "dick": "richard", "rick": "richard", "richie": "richard",
        "ed": "edward", "eddie": "edward", "eddy": "edward",
        "ted": "theodore", "teddy": "theodore",
        "al": "albert", "allie": "allison",
        "dan": "daniel", "danny": "daniel",
        "sam": "samuel", "sammy": "samuel",
        "tom": "thomas", "tommy": "thomas",
        "jim": "james", "jimmy": "james", "jamie": "james",
        "pat": "patrick", "patty": "patricia",
        "chris": "christopher",
        "ben": "benjamin", "benny": "benjamin",
        "nick": "nicholas", "nicky": "nicholas",
        "alex": "alexander",
        "matt": "matthew",
        "steve": "stephen", "stevie": "stephen",
        "dave": "david", "davey": "david",
        "tony": "anthony",
        "jenny": "jennifer", "jen": "jennifer",
        "kate": "katherine", "katie": "katherine",
        "sue": "susan", "susie": "susan",
        "peggy": "margaret", "maggie": "margaret", "meg": "margaret",
        "liz": "elizabeth", "lizzy": "elizabeth",
        "jack": "john", "johnny": "john",
        "hank": "henry",
        "chuck": "charles", "charlie": "charles",
        "larry": "lawrence",
        "jerry": "gerald",
        "terry": "terrence",
        "ray": "raymond",
        "ron": "ronald", "ronny": "ronald", "ronnie": "ronald",
        "ken": "kenneth", "kenny": "kenneth",
        "jeff": "jeffrey",
        "greg": "gregory",
        "fred": "frederick", "freddy": "frederick",
        "doug": "douglas",
        "don": "donald", "donny": "donald",
        "phil": "philip",
        "walt": "walter",
        "norm": "norman",
        "barb": "barbara",
        "deb": "deborah", "debbie": "deborah",
        "becky": "rebecca",
        "cindy": "cynthia",
        "mandy": "amanda",
        "sandy": "sandra",
        "vicky": "victoria",
        "nate": "nathaniel",
        "jake": "jacob",
        "zach": "zachary", "zack": "zachary",
    }
    # Remove formal names that should never be resolved
    NEVER_RESOLVE = {
        "john", "james", "william", "robert", "mary", "elizabeth", "michael",
        "joseph", "thomas", "charles", "david", "richard", "daniel", "matthew",
        "mark", "luke", "paul", "peter", "andrew", "george", "henry", "edward",
        "samuel", "benjamin", "alexander", "nicholas", "christopher", "stephen",
        "patrick", "margaret", "katherine", "jennifer", "susan", "patricia",
        "barbara", "sarah", "anna", "helen", "maria", "laura", "grace",
        "albert", "arthur", "frank", "ernest", "harry", "walter", "carl",
        "martin", "philip", "alan", "roger", "louis", "lawrence", "eugene",
        "raymond", "gerald", "ralph", "russell", "roy", "leon", "oscar",
        "leonard", "ernest", "harvey", "gilbert", "alfred", "lloyd",
        "stanley", "howard", "arnold", "gordon", "herman", "norm",
        "anthony", "timothy", "ronald", "gregory", "jeffrey", "frederick",
        "douglas", "donald", "norman", "theodore", "victoria",
    }
    for name in NEVER_RESOLVE:
        final.pop(name, None)
    final.update(FORCE_MAPPINGS)

    return dict(sorted(final.items()))


def main() -> None:
    log.info("Loading nickname sources...")
    all_pairs: list[tuple[str, str]] = []
    all_pairs.extend(load_carltonnorthern())
    all_pairs.extend(load_onyxrev())
    all_pairs.extend(load_diminutives())

    log.info("Building canonical mapping...")
    mapping = build_nickname_mapping(all_pairs)
    log.info(f"Total mappings: {len(mapping)}")

    # Spot-check
    for nick in ("bob", "liz", "bill", "mike", "ted"):
        log.info(f"  {nick} -> {mapping.get(nick, '(not found)')}")

    # Write output
    out_path = Path(__file__).parent.parent / "data" / "nicknames.py"
    lines = [
        '# Auto-generated by scripts/build_nicknames.py — do not edit manually.',
        '# Maps lowercase nickname → lowercase canonical (longest) form.',
        f'# {len(mapping)} entries merged from 3 open-source nickname datasets.',
        '',
        'NICKNAME_TO_CANONICAL: dict[str, str] = {',
    ]
    for nick, canonical in mapping.items():
        lines.append(f'    {json.dumps(nick)}: {json.dumps(canonical)},')
    lines.append('}')
    lines.append('')

    out_path.write_text('\n'.join(lines))
    log.info(f"Written to {out_path}")


if __name__ == "__main__":
    main()
