"""Independently verify the published synthetic example; Python 3.10+ stdlib.

No generator import, random sampling, or probability weights. This verifies the
fixed article record; it is not a tool for estimating a real player's strategy.
Checks use explicit exceptions, so python -O preserves every check.
"""
import csv
import json
import sys
from collections import Counter
from fractions import Fraction
from pathlib import Path

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


def require(condition, message):
    if not condition:
        raise ValueError(message)


def read_csv(path, columns):
    with path.open(encoding='utf-8', newline='') as source:
        reader = csv.DictReader(source)
        require(reader.fieldnames == columns, f'{path.name}: incorrect columns')
        rows = list(reader)
    require(bool(rows), f'{path.name}: empty record')
    for row in rows:
        require(set(row) == set(columns), f'{path.name}: extra fields')
        require(all(isinstance(row[k], str) and row[k] and row[k] == row[k].strip()
                    for k in columns), f'{path.name}: missing, empty or untrimmed field')
    return rows


def finite_bounds(labels):
    require(bool(labels), 'Empty denominator')
    require(all(label in ('bluff', 'value', 'unknown') for label in labels),
            'Unknown is an explicit unresolved label, not a missing field or value bet')
    counts = Counter(labels)
    n = len(labels)
    b, v, u = (counts[k] for k in ('bluff', 'value', 'unknown'))
    return (Fraction(b, n), Fraction(b + u, n),
            Fraction(b, b + v) if b + v else None)


def verify():
    # Semantic boundary cases: unknown remains unresolved and never defaults to value.
    require(finite_bounds(['unknown']) == (Fraction(0), Fraction(1), None), 'All unknown')
    require(finite_bounds(['bluff', 'value']) == (Fraction(1, 2), Fraction(1, 2), Fraction(1, 2)), 'No unknowns')
    for invalid in ([], [''], ['BLUFF'], ['fold'], [None], [' value']):
        try:
            finite_bounds(invalid)
        except ValueError:
            pass
        else:
            raise ValueError('Invalid labels accepted')

    rows = read_csv(ROOT / 'observed.csv', ['bet_id', 'observed_label'])
    expected_observed = {f'{i:02d}': 'bluff' if i <= 4 else 'value' if i <= 10 else 'unknown'
                         for i in range(1, 21)}
    observed = {row['bet_id']: row['observed_label'] for row in rows}
    require(len(rows) == len(observed) == 20, 'Observed record needs 20 unique IDs')
    require(observed == expected_observed, 'Observed labels differ from the stipulated article record')
    counts = Counter(observed.values())
    require(counts == {'bluff': 4, 'value': 6, 'unknown': 10}, 'Observed counts')
    lower, upper, classified = finite_bounds(list(observed.values()))

    # Independent bit-mask construction enumerates complete assignments to all
    # ten distinct unknown rows. Multiplicity is counted, never treated as a probability.
    totals = Counter()
    unknown_ids = sorted(k for k, label in observed.items() if label == 'unknown')
    assignments = set()
    for mask in range(1 << len(unknown_ids)):
        assignment = tuple('bluff' if mask & (1 << bit) else 'value'
                           for bit in range(len(unknown_ids)))
        assignments.add(assignment)
        completed = dict(observed)
        completed.update(zip(unknown_ids, assignment))
        require(all(completed[k] == label for k, label in observed.items() if label != 'unknown'),
                'Completion changed a known label')
        totals[sum(label == 'bluff' for label in completed.values())] += 1
    require(len(assignments) == sum(totals.values()) == 1024, 'Exhaustive assignment count')
    require(sorted(totals) == list(range(4, 15)), 'Eleven attainable totals')
    require(totals[4] == totals[14] == 1, 'Each endpoint needs one assignment')
    require((lower, upper, classified) == (Fraction(1, 5), Fraction(7, 10), Fraction(2, 5)), 'Finite fractions')

    expected_result = {
        'method': 'Exhaustive binary label completions; no probability model',
        'synthetic': True, 'total_bets': 20, 'known_bluffs': 4,
        'known_value': 6, 'unknown': 10, 'completions_checked': 1024,
        'classified_bluff_fraction': str(classified), 'lower': str(lower),
        'upper': str(upper), 'width': str(upper - lower),
        'possible_bluff_counts': sorted(totals),
        'possible_fractions': [str(Fraction(k, 20)) for k in sorted(totals)],
    }
    result = json.loads((ROOT / 'results.json').read_text(encoding='utf-8'))
    require(type(result) is dict and set(result) == set(expected_result), 'Result fields differ')
    require(all(type(result[key]) is type(value) for key, value in expected_result.items()), 'Result types differ')
    require(all(type(x) is int for x in result['possible_bluff_counts']), 'Counts must be integers')
    require(result == expected_result, 'Published results differ from independent calculation')

    endpoints = read_csv(ROOT / 'completions.csv', ['world', 'bet_id', 'observed_label', 'completed_label'])
    require(len(endpoints) == 40, 'Endpoint file needs two complete 20-row worlds')
    seen = set()
    endpoint_counts = Counter()
    for row in endpoints:
        key = (row['world'], row['bet_id'])
        require(key not in seen, 'Duplicate endpoint row')
        seen.add(key)
        require(row['world'] in ('A', 'B') and row['bet_id'] in observed, 'Invalid endpoint ID/world')
        label = observed[row['bet_id']]
        require(row['observed_label'] == label, 'Endpoint changed observed label')
        completed = label if label != 'unknown' else ('value' if row['world'] == 'A' else 'bluff')
        require(row['completed_label'] == completed, 'Endpoint violates fixed labels or its all-value/all-bluff assignment')
        endpoint_counts[row['world']] += row['completed_label'] == 'bluff'
    require(seen == {(world, key) for world in ('A', 'B') for key in observed}, 'Missing endpoint row')
    require(endpoint_counts == {'A': 4, 'B': 14}, 'Endpoint bluff counts')
    print('PASS: 20 synthetic bets; 1,024 distinct completions; 11 totals; exact 20%-70% endpoints. No probabilities assigned.')


if __name__ == '__main__':
    try:
        verify()
    except (ValueError, TypeError, KeyError, OSError, csv.Error) as error:
        print(f'FAIL: {error}', file=sys.stderr)
        raise SystemExit(1)
