#!/usr/bin/env python3
"""Local arithmetic example. No network requests, account access or output files.

Usage: python3 protection-value-demo.py protection-value-example.csv
Inputs must be allocated to a comparable scope/period before this program runs.
The script does not establish treatment assignment or causal savings.
"""
import csv
import json
import io
import re
import sys
from decimal import Decimal, ROUND_HALF_UP
from pathlib import Path

FIELDS = ['scenario', 'arm', 'currency', 'closed_customers',
          'contribution_before_acquisition', 'gross_ad_charges',
          'verified_credits', 'tool_fee', 'review_cost', 'flagged_spend_claim']
CENT = Decimal('0.01')
LIMIT = Decimal('1000000000000')


def money(value, field):
    if not re.fullmatch(r'-?[0-9]+(?:\.[0-9]{1,2})?', value or ''):
        raise ValueError(field + ': use an unformatted decimal with at most two places')
    number = Decimal(value)
    if abs(number) > LIMIT:
        raise ValueError(field + ': exceeds this example\'s amount limit')
    if field != 'contribution_before_acquisition' and number < 0:
        raise ValueError(field + ': use a non-negative amount')
    return number


def show(value):
    return str(value.quantize(CENT, rounding=ROUND_HALF_UP))


def evaluate(text):
    reader = csv.DictReader(io.StringIO(text), strict=True)
    if reader.fieldnames != FIELDS:
        raise ValueError('CSV headers/order do not match the example')
    groups = {}
    for line, row in enumerate(reader, 2):
        if None in row or any(value is None for value in row.values()):
            raise ValueError('row %s: missing or extra fields' % line)
        row = {key: value.strip() for key, value in row.items()}
        scenario, arm, currency = row['scenario'], row['arm'], row['currency']
        if not re.fullmatch(r'[a-z0-9_]{1,64}', scenario):
            raise ValueError('row %s: invalid scenario name' % line)
        if arm not in ('control', 'protected') or not re.fullmatch(r'[A-Z]{3}', currency):
            raise ValueError('row %s: invalid arm or currency' % line)
        if not re.fullmatch(r'[0-9]+', row['closed_customers']):
            raise ValueError('row %s: closed_customers must be a non-negative integer' % line)
        customers = int(row['closed_customers'])
        amounts = {field: money(row[field], field) for field in FIELDS[4:]}
        if amounts['verified_credits'] > amounts['gross_ad_charges']:
            raise ValueError('row %s: credits must belong to the selected gross charges; do not use a carried balance' % line)
        group = groups.setdefault(scenario, {})
        if arm in group:
            raise ValueError('row %s: duplicate scenario/arm' % line)
        net_ad = amounts['gross_ad_charges'] - amounts['verified_credits']
        acquisition = net_ad + amounts['tool_fee'] + amounts['review_cost']
        net = amounts['contribution_before_acquisition'] - acquisition
        # flagged_spend_claim is validated but never counted as savings.
        group[arm] = {'currency': currency, 'net': net,
                      'report': {'net_ad_cost': show(net_ad),
                                 'all_in_acquisition_cost': show(acquisition),
                                 'net_contribution': show(net),
                                 'cost_per_closed_customer': show(acquisition / customers) if customers else None}}
    if not groups:
        raise ValueError('CSV has no comparison rows')
    output = []
    for scenario, group in sorted(groups.items()):
        if set(group) != {'control', 'protected'}:
            raise ValueError(scenario + ': include exactly one control and one protected row')
        if group['control']['currency'] != group['protected']['currency']:
            raise ValueError(scenario + ': comparison currencies must match')
        output.append({'scenario': scenario, 'currency': group['control']['currency'],
                       'control': group['control']['report'],
                       'protected': group['protected']['report'],
                       'observed_net_difference': show(group['protected']['net'] - group['control']['net'])})
    return output


def main(argv):
    if len(argv) != 1:
        raise ValueError('usage: python3 protection-value-demo.py protection-value-example.csv')
    # Parse and validate every row before emitting any report.
    output = evaluate(Path(argv[0]).read_text(encoding='utf-8'))
    print(json.dumps(output, indent=2))


if __name__ == '__main__':
    try:
        main(sys.argv[1:])
    except (ValueError, OSError, csv.Error) as error:
        print('protection-value-demo: ' + str(error), file=sys.stderr)
        sys.exit(1)
