"""Exact finite-label exercise. Python 3.10+; standard library; no sampling."""
import csv
import io
import json
from fractions import Fraction
from itertools import product
from pathlib import Path

ROOT = Path(__file__).resolve().parent


def read_observed(path):
    with Path(path).open(encoding='utf-8', newline='') as stream:
        reader = csv.DictReader(stream)
        if reader.fieldnames != ['bet_id', 'observed_label']:
            raise ValueError('Expected bet_id,observed_label columns')
        rows = list(reader)
    if not rows or len(rows) > 24:
        raise ValueError('Expected 1–24 rows for this bounded enumerator')
    seen = set()
    for row in rows:
        key = row['bet_id']
        if not key or key.strip() != key or key in seen:
            raise ValueError('Bet IDs must be nonempty, trimmed and unique')
        seen.add(key)
        if row['observed_label'] not in ('bluff', 'value', 'unknown'):
            raise ValueError('Invalid observed label')
        if None in row:
            raise ValueError('Unexpected extra field')
    if sum(r['observed_label'] == 'unknown' for r in rows) > 16:
        raise ValueError('At most 16 unknowns in this bounded enumerator')
    return rows


def calculate(rows):
    known_b = sum(r['observed_label'] == 'bluff' for r in rows)
    known_v = sum(r['observed_label'] == 'value' for r in rows)
    unknown = sum(r['observed_label'] == 'unknown' for r in rows)
    n = len(rows)
    totals = set()
    count = 0
    for assignment in product(('value', 'bluff'), repeat=unknown):
        totals.add(known_b + assignment.count('bluff'))
        count += 1
    return {
        'method': 'Exhaustive binary label completions; no probability model',
        'synthetic': True,
        'total_bets': n, 'known_bluffs': known_b, 'known_value': known_v,
        'unknown': unknown, 'completions_checked': count,
        'classified_bluff_fraction': str(Fraction(known_b, known_b + known_v)) if known_b + known_v else None,
        'lower': str(Fraction(min(totals), n)),
        'upper': str(Fraction(max(totals), n)),
        'width': str(Fraction(max(totals) - min(totals), n)),
        'possible_bluff_counts': sorted(totals),
        'possible_fractions': [str(Fraction(k, n)) for k in sorted(totals)],
    }


def endpoints(rows):
    stream = io.StringIO(newline='')
    writer = csv.writer(stream, lineterminator='\n')
    writer.writerow(['world', 'bet_id', 'observed_label', 'completed_label'])
    for name, fill in [('A', 'value'), ('B', 'bluff')]:
        for row in rows:
            label = row['observed_label']
            writer.writerow([name, row['bet_id'], label, fill if label == 'unknown' else label])
    return stream.getvalue()


def main():
    rows = read_observed(ROOT / 'observed.csv')
    result = calculate(rows)
    (ROOT / 'results.json').write_text(json.dumps(result, indent=2) + '\n', encoding='utf-8', newline='\n')
    (ROOT / 'completions.csv').write_text(endpoints(rows), encoding='utf-8', newline='\n')
    print(json.dumps(result, indent=2))


if __name__ == '__main__':
    main()
