#!/usr/bin/env python3
"""Summarize normalized task costs. No network requests or price estimation."""
import json
import sys
from decimal import Decimal, InvalidOperation


def number(value):
    if isinstance(value, bool) or not isinstance(value, (str, int, float)):
        raise ValueError('Expected a nonnegative number')
    try:
        n = Decimal(str(value))
    except InvalidOperation as exc:
        raise ValueError('Invalid number') from exc
    if not n.is_finite() or n < 0:
        raise ValueError('Numbers must be finite and nonnegative')
    return n


def summarize(groups):
    if not isinstance(groups, list) or not groups:
        raise ValueError('Expected a nonempty list of groups')
    results, names = [], set()
    for group in groups:
        name = group['group']
        if not isinstance(name, str) or not name or name in names:
            raise ValueError('Group names must be unique nonempty strings')
        names.add(name)
        overhead = group['overhead_usd']
        unknown = int(overhead is None)
        known = Decimal(0) if overhead is None else number(overhead)
        accepted, attempts, review_minutes = 0, 0, Decimal(0)
        tasks, attempt_ids = set(), set()
        if not isinstance(group['tasks'], list) or not group['tasks']:
            raise ValueError('Each group requires tasks')
        for task in group['tasks']:
            key = task['task_id']
            if not isinstance(key, str) or not key or key in tasks:
                raise ValueError('Task IDs must be unique within a group')
            tasks.add(key)
            if type(task['accepted']) is not bool:
                raise ValueError('accepted must be a boolean')
            accepted += int(task['accepted'])
            review_minutes += number(task['review_minutes'])
            if not isinstance(task['attempts'], list) or not task['attempts']:
                raise ValueError('Each task requires at least one attempt')
            for attempt in task['attempts']:
                key = attempt['attempt_id']
                if not isinstance(key, str) or not key or key in attempt_ids:
                    raise ValueError('Attempt IDs must be unique within a group')
                attempt_ids.add(key)
                attempts += 1
                cost = attempt['cost_usd']
                if cost is None:
                    unknown += 1
                else:
                    known += number(cost)
        results.append(dict(group=name, tasks=len(tasks), attempts=attempts,
                            accepted=accepted, known_usd=str(known),
                            unknown_charge_entries=unknown,
                            usd_per_accepted=(str(known / accepted)
                                              if accepted and not unknown else None),
                            review_minutes=str(review_minutes)))
    return results


if __name__ == '__main__':
    try:
        with open(sys.argv[1], encoding='utf-8') as stream:
            print(json.dumps(summarize(json.load(stream)), indent=2))
    except (IndexError, KeyError, TypeError, ValueError, OSError) as exc:
        sys.exit('Invalid ledger: ' + str(exc))
