"""Describe the study datasets; Python 3 standard library only.

Default output reports counts and exploratory sensitivity analyses, not a
significance claim. --fisher provides an optional calculation for inspection;
its sampling and independence assumptions have not been established.
"""

import argparse
import csv
import hashlib
import json
from collections import Counter
from datetime import datetime, timedelta
from fractions import Fraction
from math import comb
from pathlib import Path

DATA = Path(__file__).resolve().parent
NAMES = ['si-domains.csv', 'control-domains.csv', 'com-domains.csv', 'reaction-domains.csv']
WINDOWS = [
    ('Preceding eight days', '2024-06-11', '2024-06-18'),
    ('Seven days', '2024-06-19', '2024-06-25'),
    ('Main eight-day interval', '2024-06-19', '2024-06-26'),
    ('Fourteen days', '2024-06-19', '2024-07-02'),
    ('Thirty days', '2024-06-19', '2024-07-18'),
    ('Excluding launch day', '2024-06-20', '2024-06-26'),
]


def read_sample(name):
    with (DATA / name).open(encoding='utf-8', newline='') as source:
        rows = list(csv.DictReader(source))
    seen = set()
    for row in rows:
        if row['domain'] in seen:
            raise ValueError(f'Duplicate domain in {name}: {row["domain"]}')
        seen.add(row['domain'])
        if row['status'] == 'registered':
            fmt = '%Y-%m-%d' if row['domain'].endswith('.si') else '%Y-%m-%dT%H'
            datetime.strptime(row['created'], fmt)
        elif row['status'] != 'available' or row['created']:
            raise ValueError(f'Unexpected status/date in {name}: {row}')
    return rows


def in_window(row, start, end):
    return row['status'] == 'registered' and start <= row['created'][:10] <= end


def fisher_two_sided(a, b, c, d):
    """Arithmetic only: sum fixed-margin probabilities <= the observed table."""
    first, second, hits = a + b, c + d, a + c
    denominator = comb(first + second, hits)

    def probability(x):
        return Fraction(comb(first, x) * comb(second, hits - x), denominator)

    observed = probability(a)
    possible = range(max(0, hits - second), min(first, hits) + 1)
    return sum((p for x in possible if (p := probability(x)) <= observed), Fraction())


def within_first_day(row):
    if not row['created']:
        return False
    # Conditional on the UTC timestamps and reported speech start.
    hour = datetime.strptime(row['created'], '%Y-%m-%dT%H')
    speech = datetime(2026, 9, 22, 13, 55)
    return speech <= hour and hour + timedelta(hours=1) <= speech + timedelta(days=1)


def analyze():
    samples = {name: read_sample(name) for name in NAMES}
    cognition, comparison = samples[NAMES[0]], samples[NAMES[1]]
    windows = [dict(label=label, start=start, end=end,
                    cognition=sum(in_window(r, start, end) for r in cognition),
                    comparison=sum(in_window(r, start, end) for r in comparison))
               for label, start, end in WINDOWS]
    main = windows[2]
    june = [r for r in cognition if in_window(r, main['start'], main['end'])]
    literal = [r for r in cognition if 'superintelligen' in r['domain'].split('.')[0]]
    return dict(
        input_sha256={name: hashlib.sha256((DATA / name).read_bytes()).hexdigest() for name in NAMES},
        sizes={name: len(rows) for name, rows in samples.items()},
        windows=windows,
        proportion_ratio=(main['cognition'] / len(cognition)) / (main['comparison'] / len(comparison)),
        june_dates=dict(sorted(Counter(r['created'] for r in june).items())),
        literal_subset=dict(rule='Label contains superintelligen', size=len(literal),
                            in_main_window=sum(in_window(r, main['start'], main['end']) for r in literal)),
        september={name: dict(recorded_available=sum(r['status'] == 'available' for r in samples[name]),
                              dates_september_22_23=[r['domain'] for r in samples[name] if in_window(r, '2026-09-22', '2026-09-23')],
                              conditional_first_day=[r['domain'] for r in samples[name] if within_first_day(r)])
                   for name in NAMES[2:]},
        interpretation='Descriptive counts of selected records; exploratory sensitivity analyses. No population, causal or buyer-level inference.',
    )


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--write-json', action='store_true', help='Write analysis.json beside the CSVs')
    parser.add_argument('--check', action='store_true', help='Check analysis.json against a fresh calculation')
    parser.add_argument('--fisher', action='store_true', help='Show the optional Fisher calculation with its assumptions')
    args = parser.parse_args()
    result = analyze()
    if args.write_json:
        (DATA / 'analysis.json').write_text(json.dumps(result, indent=2, ensure_ascii=False) + '\n')
    if args.check:
        if json.loads((DATA / 'analysis.json').read_text()) != result:
            raise SystemExit('analysis.json differs from the CSV calculations')
        print('analysis.json matches the saved CSVs')
    else:
        print(json.dumps(result, indent=2, ensure_ascii=False))
    if args.fisher:
        a, c = result['windows'][2]['cognition'], result['windows'][2]['comparison']
        b, d = result['sizes'][NAMES[0]] - a, result['sizes'][NAMES[1]] - c
        print('Optional calculation: sampling and independence assumptions are unverified.')
        print(f'Table: [[{a}, {b}], [{c}, {d}]]; two-sided Fisher p: {float(fisher_two_sided(a, b, c, d)):.10g}')
