"""Find teams that changed division for 2026/27 and check they are mapped.

WHY. A promoted team arrives in a division the pipeline actually trades, and
`maps.py` fails SILENTLY on it: a missing or wrong Kalshi abbreviation makes
kalshi_live fall through and 404 the per-market call, which surfaces only as a
persistent `Kalshi=?A/?H` on the dashboard (see memory/feedback_team_map_silent_miss).
Nothing raises. So the promoted set has to be checked deliberately at the start
of a season rather than waited on.

Compares each division's 2627 team list against 2526. A team appearing in a
division it was not in last season is a mover; whether that is a promotion or a
relegation is decided by the division TIER, not by the fact of moving.

Usage:  python research\\find_promoted_teams.py
"""
import csv
import io
import json
from collections import defaultdict
from pathlib import Path

LEAGUE_DIR = Path(r'E:\soccer\league_data')
TEAM_MAP = Path(r'E:\soccer\data\team_map.json')
LEAGUE_MAP = Path(r'E:\soccer\data\league_map.json')

# Tier within a country, lower = higher division. Only used to label a move as
# promotion vs relegation; countries are kept separate by the first letters.
TIER = {
    'E0': 1, 'E1': 2, 'E2': 3, 'E3': 4, 'EC': 5,          # England
    'SC0': 1, 'SC1': 2, 'SC2': 3, 'SC3': 4,               # Scotland
    'SP1': 1, 'SP2': 2,                                    # Spain
    'D1': 1, 'D2': 2,                                      # Germany
    'I1': 1, 'I2': 2,                                      # Italy
    'F1': 1, 'F2': 2,                                      # France
    'N1': 1, 'P1': 1, 'B1': 1, 'T1': 1, 'G1': 1,           # single-tier here
}
COUNTRY = {
    'E0': 'ENG', 'E1': 'ENG', 'E2': 'ENG', 'E3': 'ENG', 'EC': 'ENG',
    'SC0': 'SCO', 'SC1': 'SCO', 'SC2': 'SCO', 'SC3': 'SCO',
    'SP1': 'ESP', 'SP2': 'ESP', 'D1': 'GER', 'D2': 'GER',
    'I1': 'ITA', 'I2': 'ITA', 'F1': 'FRA', 'F2': 'FRA',
    'N1': 'NED', 'P1': 'POR', 'B1': 'BEL', 'T1': 'TUR', 'G1': 'GRE',
}


def teams_in(div, season):
    p = LEAGUE_DIR / f'{div}_{season}.csv'
    if not p.exists():
        return None
    raw = p.read_bytes().replace(b'\x00', b'')
    rows = csv.DictReader(io.StringIO(raw.decode('utf-8', errors='replace')))
    out = set()
    for r in rows:
        for k in ('HomeTeam', 'AwayTeam'):
            v = (r.get(k) or '').strip()
            if v:
                out.add(v)
    return out


def main():
    tmap = json.loads(TEAM_MAP.read_text(encoding='utf-8'))
    lmap = json.loads(LEAGUE_MAP.read_text(encoding='utf-8'))
    kalshi_divs = {d for d, v in lmap.items() if v.get('kalshi_series')}

    # fdco name -> the map entry that claims it (entries are keyed by the
    # API-Football name, so the fdco field is what league CSVs will match on)
    by_fdco = {}
    for af_name, info in tmap.items():
        if not isinstance(info, dict):
            continue
        f = info.get('fdco')
        if f:
            by_fdco.setdefault(f, []).append((af_name, info))

    divs = sorted({p.name.split('_')[0] for p in LEAGUE_DIR.glob('*_2627.csv')})
    print(f'divisions with a 2627 file: {" ".join(divs)}')
    print()

    where_2526 = defaultdict(set)     # team -> divisions it played in 2526
    for p in LEAGUE_DIR.glob('*_2526.csv'):
        d = p.name.split('_')[0]
        for t in (teams_in(d, '2526') or ()):
            where_2526[t].add(d)

    movers = []
    for d in divs:
        now = teams_in(d, '2627') or set()
        for t in sorted(now):
            prev = where_2526.get(t) or set()
            if d in prev:
                continue                      # same division, not a mover
            movers.append((d, t, sorted(prev)))

    print(f'{"div":5} {"team":28} {"was 2526":10} {"move":11} {"mapped?":8} kalshi')
    print('-' * 88)
    promoted = []
    for d, t, prev in movers:
        if prev:
            pt = min((TIER.get(x, 9) for x in prev), default=9)
            same_country = any(COUNTRY.get(x) == COUNTRY.get(d) for x in prev)
            nt = TIER.get(d, 9)
            move = ('PROMOTED' if (same_country and nt < pt)
                    else 'relegated' if (same_country and nt > pt)
                    else 'moved')
        else:
            move = 'NEW/unseen'

        hits = by_fdco.get(t) or []
        if not hits:
            mapped, kal = 'NO', '-'
        else:
            af, info = hits[0]
            kal = info.get('kalshi') or ''
            wrong_div = info.get('div') != d
            mapped = ('div=' + str(info.get('div'))) if wrong_div else 'yes'
            if not kal:
                kal = '(none)'
        flag = ''
        if d in kalshi_divs and (not hits or not (hits[0][1].get('kalshi'))):
            flag = '  <-- TRADED DIV, NOT USABLE'
        elif hits and hits[0][1].get('div') != d:
            flag = '  <-- div stale'
        print(f'{d:5} {t[:28]:28} {",".join(prev)[:10]:10} {move:11} {mapped:8} {kal}{flag}')
        if move == 'PROMOTED':
            promoted.append((d, t, hits))

    print()
    print(f'{len(movers)} movers, {len(promoted)} promoted into a division with a 2627 file')
    print()
    print('=== ACTION NEEDED (in a Kalshi-traded division, missing abbr or stale div) ===')
    need = []
    for d, t, prev in movers:
        if d not in kalshi_divs:
            continue
        hits = by_fdco.get(t) or []
        if not hits:
            need.append((d, t, 'NOT IN team_map.json at all'))
        else:
            af, info = hits[0]
            if not info.get('kalshi'):
                need.append((d, t, f'no kalshi abbr (af name: {af})'))
            elif info.get('div') != d:
                need.append((d, t, f'div says {info.get("div")}, should be {d} (af name: {af})'))
    if not need:
        print('  none - every mover in a traded division has an abbr and the right div')
    for d, t, why in need:
        print(f'  {d:5} {t:30} {why}')


if __name__ == '__main__':
    main()
