#!/usr/bin/env python3
"""Compare a manually matched migration ledger. Standard library only.

python3 verify_migration.py expected.csv observed.csv
Exit 0: this ledger's checks passed. Exit 1: mismatches. No account access.
This does not parse workout files, match identities, inspect charts or prove
that a service accepted an upload. All supplied example rows are fictional.
"""
from collections import Counter
import csv
from datetime import datetime
import json
from pathlib import Path
import sys

def load(path):
    with Path(path).open(newline="") as stream:
        return list(csv.DictReader(stream))

def instant(value):
    date = datetime.fromisoformat(value.replace("Z", "+00:00"))
    if date.tzinfo is None:
        raise ValueError("start_time needs Z or a UTC offset")
    return date.timestamp()

def verify(expected, observed):
    keys = [row["source_key"] for row in expected]
    if len(set(keys)) != len(keys):
        raise ValueError("Expected ledger contains duplicate source keys")
    counts = Counter(row["source_key"] for row in observed)
    issues = []
    for key in keys:
        if counts[key] == 0:
            issues.append(dict(source_key=key, issue="missing activity"))
        elif counts[key] > 1:
            issues.append(dict(source_key=key, issue="duplicate activity", count=counts[key]))
    for row in observed:
        key = row["source_key"]
        if key not in keys:
            issues.append(dict(source_key=key, issue="unexpected activity"))
            continue
        source = next(r for r in expected if r["source_key"] == key)
        if instant(source["start_time"]) != instant(row["start_time"]):
            issues.append(dict(source_key=key, issue="different start instant"))
        if source["sport"] != row["sport"]:
            issues.append(dict(source_key=key, issue="different sport"))
        if abs(float(source["elapsed_seconds"]) - float(row["elapsed_seconds"])) > 1:
            issues.append(dict(source_key=key, issue="elapsed time differs by more than 1 second"))
        if source["distance_meters"]:
            if not row["distance_meters"] or abs(float(source["distance_meters"]) - float(row["distance_meters"])) > 1:
                issues.append(dict(source_key=key, issue="distance missing or differs by more than 1 metre"))
        for field in ["has_route", "has_heart_rate", "has_power"]:
            if source[field] not in ["0", "1"] or row[field] not in ["0", "1"]:
                raise ValueError("Presence fields must be 0 or 1")
            if source[field] == "1" and row[field] != "1":
                issues.append(dict(source_key=key, issue="missing " + field.removeprefix("has_")))
    return dict(expected_count=len(expected), observed_count=len(observed),
                checked="Manually matched identities, UTC start instant, sport, elapsed time ±1 s, known distance ±1 m and required field presence. Not stream-value, attachment or destination acceptance verification.",
                passed=not issues, issues=issues)

if __name__ == "__main__":
    result = verify(load(sys.argv[1]), load(sys.argv[2]))
    print(json.dumps(result, indent=2))
    sys.exit(0 if result["passed"] else 1)
