#!/usr/bin/env python3
"""Comparer des essais vidéo consignés en CSV, sans appeler d'API.

Python 3.10+, bibliothèque standard. Les données --demo sont fictives.
Une ligne par tentative. Coût inconnu ou contrôle en attente : bilan provisoire.
"""
import argparse
from collections import defaultdict
import csv
from decimal import Decimal, InvalidOperation
import json
from pathlib import Path
from statistics import median

FIELDS = ['attempt_id', 'config_id', 'brief_id', 'status', 'billed_usd',
          'accepted', 'latency_s', 'is_synthetic']
STATUSES = {'succeeded', 'failed', 'unknown'}


def number(value, field):
    if not value:
        return None
    try:
        n = Decimal(value)
    except InvalidOperation as exc:
        raise ValueError(f'{field}: nombre invalide') from exc
    if not n.is_finite() or n < 0:
        raise ValueError(f'{field}: nombre fini et positif ou nul requis')
    return n


def boolean(value, field, optional=False):
    if optional and value == '':
        return None
    if value not in {'true', 'false'}:
        raise ValueError(f'{field}: true ou false requis')
    return value == 'true'


def load(path):
    records, seen = [], set()
    with path.open(encoding='utf-8-sig', newline='') as file:
        reader = csv.DictReader(file)
        if reader.fieldnames != FIELDS:
            raise ValueError('Colonnes attendues : ' + ','.join(FIELDS))
        for line, row in enumerate(reader, 2):
            if None in row or any(v is None for v in row.values()):
                raise ValueError(f'Ligne {line}: nombre de colonnes incorrect')
            row = {k: v.strip() for k, v in row.items()}
            if any(not row[k] for k in ['attempt_id', 'config_id', 'brief_id']):
                raise ValueError(f'Ligne {line}: identifiant manquant')
            key = (row['config_id'], row['attempt_id'])
            if key in seen:
                raise ValueError(f'Ligne {line}: tentative en double {key}')
            seen.add(key)
            if row['status'] not in STATUSES:
                raise ValueError(f'Ligne {line}: statut invalide')
            row['accepted'] = boolean(row['accepted'], 'accepted', optional=True)
            if row['status'] != 'succeeded' and row['accepted'] is not False:
                raise ValueError('Une tentative non réussie doit avoir accepted=false')
            row['billed_usd'] = number(row['billed_usd'], 'billed_usd')
            row['latency_s'] = number(row['latency_s'], 'latency_s')
            row['is_synthetic'] = boolean(row['is_synthetic'], 'is_synthetic')
            records.append(row)
    if not records:
        raise ValueError('Le CSV ne contient aucun essai')
    if len({r['is_synthetic'] for r in records}) > 1:
        raise ValueError('Ne pas mélanger essais réels et données synthétiques')
    return records


def summarize(records):
    groups = defaultdict(list)
    for row in records:
        groups[row['config_id']].append(row)
    results = {}
    all_briefs = {r['brief_id'] for r in records}
    for config, rows in sorted(groups.items()):
        accepted = [r for r in rows if r['accepted'] is True]
        pending = sum(r['accepted'] is None for r in rows)
        unknown_cost = sum(r['billed_usd'] is None for r in rows)
        unknown_status = sum(r['status'] == 'unknown' for r in rows)
        complete = pending == unknown_cost == unknown_status == 0
        cost = sum((r['billed_usd'] for r in rows if r['billed_usd'] is not None), Decimal(0))
        successful = [r for r in rows if r['status'] == 'succeeded']
        timings = [r['latency_s'] for r in successful if r['latency_s'] is not None]
        results[config] = {
            'attempts': len(rows), 'successful': len(successful), 'accepted': len(accepted),
            'acceptance_rate_per_attempt': len(accepted) / len(rows) if complete else None,
            'briefs_tested': len({r['brief_id'] for r in rows}),
            'briefs_with_accepted_video': len({r['brief_id'] for r in accepted}),
            'missing_briefs': sorted(all_briefs - {r['brief_id'] for r in rows}),
            'pending_reviews': pending, 'unknown_costs': unknown_cost,
            'unknown_statuses': unknown_status, 'complete': complete,
            'billed_total_usd': str(cost) if unknown_cost == 0 else None,
            'cost_per_accepted_usd': str((cost / len(accepted)).quantize(Decimal('0.0001'))) if complete and accepted else None,
            'successful_latency_median_s': float(median(timings)) if timings and len(timings) == len(successful) else None,
            'successful_latency_sample_size': len(timings),
        }
    return {'synthetic_data': records[0]['is_synthetic'], 'results': results,
            'note': 'Coût API seulement. Pas de classement automatique ni de preuve de qualité des fournisseurs.'}


def demo(path):
    # Six tentatives par configuration, deux essais pour chacun des trois briefs.
    # A et B ne désignent aucun fournisseur ni tarif réel.
    rows = []
    for config, price, good in [('A', '0.60', {1, 3}), ('B', '0.80', {1, 2, 3, 5})]:
        for i in range(1, 7):
            failed = config == 'A' and i == 6
            rows.append([f'{config}-{i}', config, f'brief-{(i+1)//2}',
                         'failed' if failed else 'succeeded', '0' if failed else price,
                         'true' if i in good else 'false', str(20 + i * 5), 'true'])
    with path.open('x', encoding='utf-8', newline='') as file:
        writer = csv.writer(file)
        writer.writerow(FIELDS)
        writer.writerows(rows)


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('csv', type=Path)
    parser.add_argument('--demo', action='store_true', help='Créer un CSV neuf de données fictives')
    args = parser.parse_args()
    try:
        if args.demo:
            demo(args.csv)
        print(json.dumps(summarize(load(args.csv)), ensure_ascii=False, indent=2))
    except (ValueError, OSError) as exc:
        parser.exit(2, f'Erreur : {exc}\n')


if __name__ == '__main__':
    main()
