#!/usr/bin/env python3
"""Log and summarize local AI workflow experiments. Python 3.9+, stdlib only."""

import argparse
import csv
from datetime import datetime, timezone
from decimal import Decimal, InvalidOperation
from pathlib import Path
import sys


FIELDS = [
    "recorded_at_utc", "workflow", "result", "run_minutes", "review_minutes",
    "correction_minutes", "total_minutes",
]


def minutes(value, maximum=Decimal("1000000")):
    try:
        number = Decimal(value)
    except InvalidOperation as exc:
        raise argparse.ArgumentTypeError("Enter a nonnegative number of minutes.") from exc
    if not number.is_finite() or number < 0 or number > maximum:
        raise argparse.ArgumentTypeError(f"Minutes must be between 0 and {maximum:,}.")
    if number.as_tuple().exponent < -6:
        raise argparse.ArgumentTypeError("Use at most six decimal places.")
    return number


def workflow_name(value):
    value = value.strip()
    if not value or len(value) > 200 or any(ord(char) < 32 for char in value):
        raise argparse.ArgumentTypeError("Use a workflow name of 1–200 characters without control characters.")
    # Keep spreadsheet applications from interpreting a name as a formula.
    if value.startswith(("=", "+", "-", "@")):
        value = "'" + value
    return value


def read_rows(handle):
    handle.seek(0)
    reader = csv.DictReader(handle)
    if reader.fieldnames != FIELDS:
        raise ValueError("CSV header does not match this script's format; choose a different file.")
    rows = list(reader)
    for index, row in enumerate(rows, start=2):
        if None in row or any(value is None for value in row.values()):
            raise ValueError(f"CSV row {index} has the wrong number of fields.")
        if row["result"] not in ("pass", "fail"):
            raise ValueError(f"CSV row {index} has an invalid result.")
        try:
            values = [
                minutes(row[field], Decimal("3000000") if field == "total_minutes" else Decimal("1000000"))
                for field in FIELDS[3:]
            ]
        except argparse.ArgumentTypeError as exc:
            raise ValueError(f"CSV row {index} has invalid timing data.") from exc
        if sum(values[:3]) != values[3]:
            raise ValueError(f"CSV row {index} has an inconsistent total.")
    return rows


def log_run(args):
    path = args.csv.expanduser()
    total = args.run_minutes + args.review_minutes + args.correction_minutes
    row = {
        "recorded_at_utc": datetime.now(timezone.utc).isoformat(timespec="seconds"),
        "workflow": args.workflow,
        "result": args.result,
        "run_minutes": format(args.run_minutes, "f"),
        "review_minutes": format(args.review_minutes, "f"),
        "correction_minutes": format(args.correction_minutes, "f"),
        "total_minutes": format(total, "f"),
    }
    # a+ preserves existing bytes. Validate a nonempty file before appending.
    # Run a single instance at a time; this intentionally has no shared-file locking.
    with path.open("a+", encoding="utf-8", newline="") as handle:
        handle.seek(0, 2)
        empty = handle.tell() == 0
        if not empty:
            read_rows(handle)
            handle.seek(0)
            final_character = handle.read()[-1:]
            handle.seek(0, 2)
            if final_character not in ("\n", "\r"):
                handle.write("\n")
        writer = csv.DictWriter(handle, fieldnames=FIELDS)
        if empty:
            writer.writeheader()
        writer.writerow(row)
    print(f"Logged {args.result}: {total} total minutes → {path}")


def summarize(args):
    path = args.csv.expanduser()
    with path.open("r", encoding="utf-8", newline="") as handle:
        rows = read_rows(handle)
    if not rows:
        print("No runs logged yet.")
        return
    passed = sum(row["result"] == "pass" for row in rows)
    total = sum(Decimal(row["total_minutes"]) for row in rows)
    review = sum(Decimal(row["review_minutes"]) for row in rows)
    correction = sum(Decimal(row["correction_minutes"]) for row in rows)
    print(f"Runs: {len(rows)} | Passed: {passed} | Failed: {len(rows) - passed}")
    print(f"Total effort: {total} min | Review: {review} min | Correction: {correction} min")
    print(f"Average total effort per run: {total / len(rows):.2f} min")


def main():
    parser = argparse.ArgumentParser(
        description="Log AI workflow experiment time and pass/fail results to a local CSV.",
        epilog="No network requests. Use non-sensitive workflow names. Run one instance at a time.",
    )
    commands = parser.add_subparsers(dest="command", required=True)
    log = commands.add_parser("log", help="Append one experiment result.")
    log.add_argument("--csv", type=Path, required=True, help="Local CSV path; parent directory must exist.")
    log.add_argument("--workflow", type=workflow_name, required=True, help="Short, non-sensitive workflow name.")
    log.add_argument("--result", choices=("pass", "fail"), required=True, help="Did the run meet your predefined checks?")
    log.add_argument("--run-minutes", type=minutes, required=True, help="Initial run time, before review or correction.")
    log.add_argument("--review-minutes", type=minutes, required=True, help="Time spent checking the result.")
    log.add_argument("--correction-minutes", type=minutes, default=Decimal(0), help="Time spent fixing the result (default: 0).")
    log.set_defaults(action=log_run)
    summary = commands.add_parser("summary", help="Print counts and total effort across the CSV.")
    summary.add_argument("--csv", type=Path, required=True, help="Existing CSV created by this script.")
    summary.set_defaults(action=summarize)
    args = parser.parse_args()
    try:
        args.action(args)
    except (OSError, UnicodeError, csv.Error, ValueError) as exc:
        print(f"Error: {exc}", file=sys.stderr)
        return 1
    return 0


if __name__ == "__main__":
    sys.exit(main())
