#!/usr/bin/env python3
"""Data study #1: Remote start coverage of 2010-2026 vehicles by make.

Source: ../../../vehicles.csv (RemoteStarterCheck dataset, built from Fortin
public guide index pages, Fortin guide PDFs (page 1) and Fortin vehicle pages,
all checked 2026-10-16). Run: python3 coverage_study.py
Outputs (same folder): coverage_by_make.csv, coverage_by_ignition.csv,
coverage_by_start_year.csv, summary.json
"""
import csv, json, os, re
from collections import defaultdict

HERE = os.path.dirname(os.path.abspath(__file__))
SRC = os.path.normpath(os.path.join(HERE, '..', '..', '..', 'vehicles.csv'))

VERIFIED = 'Verified: Fortin guide PDF p.1 vehicle table'
INDEX = ('Fortin guide index lists vehicle; p.1 table match not found',
         'Fortin guide index only (p.1 not confirmed)')
LISTED = 'Listed only: no guide confirmed on p.1'
EV = 'Not applicable: electric vehicle'

def ign_class(s):
    t = s.lower()
    if 'electric (no engine)' in t:
        return 'Electric (no engine)'
    if re.search(r'push|intelligent key|smart key|proximity|fast keyless', t):
        return 'Push-to-start'
    if re.search(r'\bkey\b|key port|transponder', t):
        return 'Turn key'
    return 'Not stated in source'

def years(yr):
    p = [int(x) for x in re.findall(r'\d{4}', yr)]
    return (p[0], p[-1]) if p else (None, None)

def bucket(cnt):
    n = cnt['configs']
    return dict(configs=n, verified=cnt['verified'], index_listed=cnt['index'],
                listed_only=cnt['listed'],
                verified_pct=round(100 * cnt['verified'] / n, 1) if n else None,
                any_guide_pct=round(100 * (cnt['verified'] + cnt['index']) / n, 1) if n else None)

def tally(rows, keyf):
    out = defaultdict(lambda: defaultdict(int))
    for r in rows:
        k = keyf(r); c = out[k]; c['configs'] += 1
        st = r['status']
        if st == VERIFIED: c['verified'] += 1
        elif st in INDEX: c['index'] += 1
        elif st == LISTED: c['listed'] += 1
    return out

rows = list(csv.DictReader(open(SRC, newline='', encoding='utf-8')))
total_rows = len(rows)
status_counts = defaultdict(int)
for r in rows: status_counts[r['status']] += 1
ev_rows = [r for r in rows if r['status'] == EV]
cars = [r for r in rows if r['status'] != EV]  # engine vehicles only
for r in cars:
    r['_ign'] = ign_class(r['ignition_type'])
    r['_y0'], r['_y1'] = years(r['year_range'])

# by make
bm = tally(cars, lambda r: r['make'])
models = defaultdict(set)
for r in cars: models[r['make']].add(r['model'])
make_rows = []
for mk, c in bm.items():
    b = bucket(c); b = dict(make=mk, models=len(models[mk]), **b)
    b['meets_n20'] = 'yes' if c['configs'] >= 20 else 'no (small sample)'
    make_rows.append(b)
make_rows.sort(key=lambda x: (-x['configs'], x['make']))
cols = ['make', 'models', 'configs', 'verified', 'index_listed', 'listed_only',
        'verified_pct', 'any_guide_pct', 'meets_n20']
with open(os.path.join(HERE, 'coverage_by_make.csv'), 'w', newline='') as f:
    w = csv.DictWriter(f, fieldnames=cols); w.writeheader(); w.writerows(make_rows)

# by ignition class
bi = tally(cars, lambda r: r['_ign'])
ign_rows = [dict(ignition_class=k, **bucket(c)) for k, c in bi.items()]
ign_rows.sort(key=lambda x: -x['configs'])
with open(os.path.join(HERE, 'coverage_by_ignition.csv'), 'w', newline='') as f:
    w = csv.DictWriter(f, fieldnames=['ignition_class', 'configs', 'verified', 'index_listed',
                                      'listed_only', 'verified_pct', 'any_guide_pct'])
    w.writeheader(); w.writerows(ign_rows)

# by first model year of the configuration, grouped
def yb(r):
    y = r['_y0']
    if y is None: return 'unknown'
    if y <= 2012: return '2010-2012'
    if y <= 2015: return '2013-2015'
    if y <= 2018: return '2016-2018'
    if y <= 2021: return '2019-2021'
    if y <= 2024: return '2022-2024'
    return '2025-2026'
by = tally(cars, yb)
yr_rows = [dict(first_model_year=k, **bucket(c)) for k, c in sorted(by.items())]
with open(os.path.join(HERE, 'coverage_by_start_year.csv'), 'w', newline='') as f:
    w = csv.DictWriter(f, fieldnames=['first_model_year', 'configs', 'verified', 'index_listed',
                                      'listed_only', 'verified_pct', 'any_guide_pct'])
    w.writeheader(); w.writerows(yr_rows)

# push-to-start configurations starting 2015 or later
p15 = [r for r in cars if r['_ign'] == 'Push-to-start' and r['_y0'] and r['_y0'] >= 2015]
p15v = sum(r['status'] == VERIFIED for r in p15)
# verified rows: harness and firmware documented?
ver = [r for r in cars if r['status'] == VERIFIED]
har = sum(r['t_harness'].startswith('THAR') for r in ver)
fw = sum(bool(re.match(r'^\d+\.\d+', r['min_firmware'])) for r in ver)
mods = defaultdict(int)
for r in ver: mods[r['module']] += 1
hyb = [r for r in cars if re.search(r'hybrid|phev|mhev|plug in|4xe', r['ignition_type'].lower())]

big = [m for m in make_rows if m['configs'] >= 20]
summary = dict(
    source=os.path.basename(SRC), source_checked='2026-10-16', total_rows=total_rows,
    status_counts=dict(status_counts), ev_rows_excluded=len(ev_rows),
    engine_configs=len(cars), makes=len(bm), models=sum(len(v) for v in models.values()),
    min_year=min(r['_y0'] for r in cars if r['_y0']), max_year=max(r['_y1'] for r in cars if r['_y1']),
    verified=len(ver), verified_pct=round(100 * len(ver) / len(cars), 1),
    any_guide=len(ver) + sum(r['status'] in INDEX for r in cars),
    any_guide_pct=round(100 * (len(ver) + sum(r['status'] in INDEX for r in cars)) / len(cars), 1),
    push_2015plus=len(p15), push_2015plus_verified=p15v,
    push_2015plus_verified_pct=round(100 * p15v / len(p15), 1),
    verified_with_tharness=har, verified_with_tharness_pct=round(100 * har / len(ver), 1),
    verified_with_firmware=fw, verified_modules=dict(mods),
    hybrid_configs=len(hyb), hybrid_verified=sum(r['status'] == VERIFIED for r in hyb),
    top_verified_makes_n20=[(m['make'], m['verified_pct'], m['configs']) for m in sorted(big, key=lambda x: -x['verified_pct'])[:5]],
    bottom_verified_makes_n20=[(m['make'], m['verified_pct'], m['configs']) for m in sorted(big, key=lambda x: x['verified_pct'])[:5]],
    by_ignition=ign_rows, by_start_year=yr_rows)
json.dump(summary, open(os.path.join(HERE, 'summary.json'), 'w'), indent=2)
print(json.dumps({k: v for k, v in summary.items() if k not in ('by_ignition', 'by_start_year')}, indent=2))
for x in ign_rows: print(x)
for x in yr_rows: print(x)


# Optional chart (needs matplotlib). Makes with at least 20 configurations only.
try:
    import matplotlib
    matplotlib.use('Agg')
    import matplotlib.pyplot as plt
    rows_ = sorted(big, key=lambda x: x['verified_pct'])
    fig, ax = plt.subplots(figsize=(8, 7))
    ax.barh([m['make'] for m in rows_], [m['verified_pct'] for m in rows_], color='#c8102e')
    for i, m in enumerate(rows_):
        ax.text(m['verified_pct'] + 1, i, f"{m['verified_pct']}% (n={m['configs']})", va='center', fontsize=8)
    ax.set_xlim(0, 105)
    ax.set_xlabel('Share of configurations with a verified Fortin guide (%)')
    ax.set_title('Verified remote start coverage by make, 2010-2026\n(makes with 20+ configurations, RemoteStarterCheck data checked 2026-10-16)', fontsize=10)
    plt.tight_layout()
    plt.savefig(os.path.join(HERE, 'coverage_by_make.png'), dpi=150)
except ImportError:
    pass
