#!/usr/bin/env python3
"""Server-side application projections over v1 samples. Offline synthetic fixtures."""

import argparse
import json
import math
import time


def latest_fix(samples):
    fixes = [s for s in samples if s["kind"] == "location"]
    return max(fixes, key=lambda s: s["ts"], default=None)


def position(member, samples, audience, grants, now):
    # Check the subject, audience and capture window as well as the current expiry.
    grant = next(
        (
            g
            for g in grants
            if g["member"] == member
            and g["audience"] == audience
            and g["start"] <= now < g["end"]
            and not g["revoked"]
        ),
        None,
    )
    if not grant:
        return None
    fix = latest_fix([s for s in samples if grant["start"] <= s["ts"] <= now])
    if not fix or now - fix["ts"] > 120000:
        return None
    return {k: round(fix["values"][k], grant["decimals"]) for k in ("lat", "lon")} | {
        "ts": fix["ts"]
    }


def distance_m(a, b):
    lat1, lat2 = map(math.radians, (a["lat"], b["lat"]))
    dlon = math.radians(b["lon"] - a["lon"])
    h = (
        math.sin((lat2 - lat1) / 2) ** 2
        + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2
    )
    return 6371008.8 * 2 * math.asin(math.sqrt(min(1, h)))


def demo(recipe):
    now = int(time.time() * 1000)
    rows = {
        m: [{"kind": "location", "ts": now - 5000, "values": {"lat": lat, "lon": lon}}]
        for m, lat, lon in [("rider-A", 28.61, 77.21), ("rider-B", 28.62, 77.22)]
    }
    rows["rider-B"].append(
        {
            "kind": "incident",
            "ts": now - 1000,
            "values": {"type": "acceleration", "magnitude": 14.2},
        }
    )
    grants = [
        dict(
            member=m,
            audience=recipe,
            start=now - 60000,
            end=now + 60000,
            revoked=False,
            decimals=3 if recipe == "family" else 5,
        )
        for m in rows
    ]
    if recipe == "family":
        grants[1]["revoked"] = True
    if recipe == "fleet":
        active_shift = {"rider-B"}
        return [
            {"assigned": m, "kind": s["kind"], "ts": s["ts"], "details": s["values"]}
            for m in active_shift
            for s in rows[m]
            if s["kind"] == "incident"
        ]
    positions = {m: p for m in rows if (p := position(m, rows[m], recipe, grants, now))}
    if recipe == "taxi":
        # The app backend owns availability. Reserve a candidate transactionally before dispatch.
        available = {"rider-A"}
        pickup = {"lat": 28.611, "lon": 77.211}
        return sorted(
            [
                {"candidate": m, "straightLineMetres": round(distance_m(p, pickup))}
                for m, p in positions.items()
                if m in available
            ],
            key=lambda c: c["straightLineMetres"],
        )
    if recipe == "caravan" and len(positions) == 2:
        return {
            "positions": positions,
            "separationMetres": round(distance_m(*positions.values())),
        }
    return positions


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("recipe", choices=("family", "caravan", "fleet", "taxi"))
    print(json.dumps(demo(parser.parse_args().recipe), indent=2))
