import { useCallback, useEffect, useState, type FormEvent } from "react";
import { api, apiObjectUrl } from "../lib/api";

type Driver = {
  id: number;
  first_name: string;
  last_name: string;
  profile_image_url: string | null;
  car_image_url: string | null;
  car_seats: number;
  rating: string;
  service: string;
  online: boolean;
  car_model: string | null;
  total_rides: number;
  revenue: number;
  approval_status: string;
  rejection_reason: string | null;
  submitted_at: string | null;
  // What the driver typed at onboarding, usually read off the scans below.
  license_number: string | null;
  license_expiry: string | null;
  national_id: string | null;
  plate_number: string | null;
  // Stored scan names, served through /driver/documents/:name.
  license_image_url: string | null;
  id_image_url: string | null;
  vehicle_reg_image_url: string | null;
};

// What each driver still owes the company, and what the company still owes
// them. Loaded alongside the driver list so an operator can reconcile a shift
// without leaving the page.
type Balance = {
  driver_id: number;
  name: string;
  owes_company_cents: number;
  owed_to_driver_cents: number;
  unsettled_rides: number;
};

const money = (cents: number) => (cents / 100).toFixed(2);

const EMPTY = {
  first_name: "",
  last_name: "",
  profile_image_url: "",
  car_image_url: "",
  car_seats: 4,
  rating: 4.5,
};

export default function Drivers() {
  const [drivers, setDrivers] = useState<Driver[]>([]);
  const [error, setError] = useState<string | null>(null);
  const [editing, setEditing] = useState<Driver | "new" | null>(null);
  const [balances, setBalances] = useState<Record<number, Balance>>({});

  // Vetting a driver against their scans. Separate from the edit form: this is
  // a decision about whether someone may carry passengers, not a field update.
  const [reviewing, setReviewing] = useState<Driver | null>(null);

  // Opening the picker rather than settling outright. A driver handing over
  // part of what they owe is normal, and settling the whole balance because
  // the button only offered all-or-nothing would put the ledger out of step
  // with the cash actually received.
  const [settleFor, setSettleFor] = useState<{
    driver: Driver;
    side: "platform_fee" | "driver_payout";
  } | null>(null);

  const load = useCallback(async () => {
    try {
      const [res, ledger] = await Promise.all([
        api<{ data: Driver[] }>("/admin/drivers"),
        api<{ data: Balance[] }>("/admin/settle"),
      ]);
      setDrivers(res.data);
      setBalances(
        Object.fromEntries(ledger.data.map((b) => [b.driver_id, b])),
      );
      setError(null);
    } catch (e) {
      setError((e as Error).message);
    }
  }, []);

  useEffect(() => {
    load();
  }, [load]);

  const remove = async (id: number) => {
    if (!confirm("Delete this driver?")) return;
    try {
      await api(`/admin/drivers/${id}`, { method: "DELETE" });
      await load();
    } catch (e) {
      setError((e as Error).message);
    }
  };

  return (
    <>
      <h2>Drivers & fleet</h2>
      <div className="toolbar">
        <button onClick={() => setEditing("new")}>Add driver</button>
      </div>
      {error && <div className="error">{error}</div>}
      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Service</th>
            <th>Seats</th>
            <th>Rating</th>
            <th>Vetting</th>
            <th>Online</th>
            <th>Rides</th>
            <th>Revenue</th>
            <th>Owes company</th>
            <th>Owed to driver</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          {drivers.map((d) => (
            <tr key={d.id}>
              <td>{d.id}</td>
              <td>
                {d.first_name} {d.last_name}
              </td>
              <td>
                <span className={`tag tag-${d.service}`}>{d.service}</span>
              </td>
              <td>{d.car_seats}</td>
              <td>{d.rating}</td>
              <td>
                <span className={`badge ${d.approval_status}`}>
                  {d.approval_status}
                </span>
              </td>
              <td>{d.online ? "● online" : "○ off"}</td>
              <td>{d.total_rides}</td>
              <td>{d.revenue.toLocaleString()}</td>
              <td>
                {balances[d.id]?.owes_company_cents ? (
                  <strong>{money(balances[d.id].owes_company_cents)}</strong>
                ) : (
                  <span className="muted">—</span>
                )}
              </td>
              <td>
                {balances[d.id]?.owed_to_driver_cents ? (
                  <strong>{money(balances[d.id].owed_to_driver_cents)}</strong>
                ) : (
                  <span className="muted">—</span>
                )}
              </td>
              <td>
                <div className="row-actions">
                  {balances[d.id]?.owes_company_cents ? (
                    <button
                      className="secondary"
                      onClick={() =>
                        setSettleFor({ driver: d, side: "platform_fee" })
                      }
                    >
                      Collect
                    </button>
                  ) : null}
                  {balances[d.id]?.owed_to_driver_cents ? (
                    <button
                      className="secondary"
                      onClick={() =>
                        setSettleFor({ driver: d, side: "driver_payout" })
                      }
                    >
                      Pay out
                    </button>
                  ) : null}
                  <button className="secondary" onClick={() => setReviewing(d)}>
                    Review
                  </button>
                  <button className="secondary" onClick={() => setEditing(d)}>
                    Edit
                  </button>
                  <button className="danger" onClick={() => remove(d.id)}>
                    Delete
                  </button>
                </div>
              </td>
            </tr>
          ))}
        </tbody>
      </table>

      {settleFor && (
        <SettlePicker
          driver={settleFor.driver}
          side={settleFor.side}
          onClose={() => setSettleFor(null)}
          onSettled={() => {
            setSettleFor(null);
            load();
          }}
        />
      )}

      {reviewing && (
        <VettingPanel
          driver={reviewing}
          onClose={() => setReviewing(null)}
          onDecided={() => {
            setReviewing(null);
            load();
          }}
        />
      )}

      {editing && (
        <DriverForm
          initial={editing === "new" ? null : editing}
          onClose={() => setEditing(null)}
          onSaved={() => {
            setEditing(null);
            load();
          }}
        />
      )}
    </>
  );
}

// One document scan, fetched with the operator's token and rendered from a
// blob URL — the route is authenticated, so a bare <img src> would 401.
function DocumentScan({ name, label }: { name: string; label: string }) {
  const [src, setSrc] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    let url: string | null = null;
    let cancelled = false;

    apiObjectUrl(`/driver/documents?name=${encodeURIComponent(name)}`)
      .then((objectUrl) => {
        url = objectUrl;
        // The panel may have closed while the fetch was in flight; revoke
        // rather than setting state on an unmounted component.
        if (cancelled) URL.revokeObjectURL(objectUrl);
        else setSrc(objectUrl);
      })
      .catch((e) => {
        if (!cancelled) setError((e as Error).message);
      });

    return () => {
      cancelled = true;
      if (url) URL.revokeObjectURL(url);
    };
  }, [name]);

  return (
    <figure style={{ margin: 0 }}>
      <figcaption className="muted" style={{ fontSize: 12, marginBottom: 4 }}>
        {label}
      </figcaption>
      {error ? (
        <div className="error">{error}</div>
      ) : src ? (
        // Opens full size in a tab: small print on a licence is unreadable at
        // thumbnail size, and reading it is the whole point of this panel.
        <a href={src} target="_blank" rel="noreferrer">
          <img
            src={src}
            alt={label}
            style={{
              width: "100%",
              maxHeight: 220,
              objectFit: "contain",
              background: "#00000010",
              borderRadius: 8,
            }}
          />
        </a>
      ) : (
        <div className="muted">Loading…</div>
      )}
    </figure>
  );
}

// The driver's profile photo. Unlike a document scan this route is public, so
// the browser can load it straight from a <src> — a stored name is resolved
// through the API, while an external URL an owner typed in is used as-is.
function DriverPhoto({ name }: { name: string }) {
  const src = /^https?:/i.test(name)
    ? name
    : `${import.meta.env.VITE_API_URL ?? ""}/driver/photo?name=${encodeURIComponent(name)}`;

  return (
    <img
      src={src}
      alt="Driver profile photo"
      style={{
        width: 72,
        height: 72,
        borderRadius: "50%",
        objectFit: "cover",
        background: "#00000010",
      }}
    />
  );
}

/**
 * Check what a driver typed against the documents they photographed, then
 * approve or reject.
 *
 * The scans exist precisely because the typed numbers arrive from OCR and OCR
 * is fallible — so the two are shown side by side and the decision rests on
 * the document, not on the field. Rejecting requires a reason, which is what
 * the driver sees in the app and corrects against.
 */
function VettingPanel({
  driver,
  onClose,
  onDecided,
}: {
  driver: Driver;
  onClose: () => void;
  onDecided: () => void;
}) {
  const [reason, setReason] = useState(driver.rejection_reason ?? "");
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const decide = async (
    approval_status: "approved" | "rejected" | "suspended",
  ) => {
    if (approval_status !== "approved" && !reason.trim()) {
      setError("Give the driver a reason they can act on.");
      return;
    }

    setBusy(true);
    setError(null);
    try {
      await api(`/admin/drivers/${driver.id}`, {
        method: "PATCH",
        body: JSON.stringify({
          approval_status,
          rejection_reason:
            approval_status === "approved" ? undefined : reason.trim(),
        }),
      });
      onDecided();
    } catch (e) {
      setError((e as Error).message);
    } finally {
      setBusy(false);
    }
  };

  const scans: [string | null, string][] = [
    [driver.license_image_url, "Driving licence"],
    [driver.id_image_url, "ID card"],
    [driver.vehicle_reg_image_url, "Vehicle registration"],
  ];

  const present = scans.filter(([name]) => name);

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <h3>
          Vetting — {driver.first_name} {driver.last_name} (#{driver.id})
        </h3>

        {/* The photo riders will actually see. It is checked here rather than
            left to chance because it is the one part of the profile shown to
            every passenger before they get into the car. */}
        {driver.profile_image_url && (
          <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
            <DriverPhoto name={driver.profile_image_url} />
            <span className="muted" style={{ fontSize: 12 }}>
              Shown to riders choosing a driver
            </span>
          </div>
        )}

        <p className="muted" style={{ marginTop: 0 }}>
          Status: <strong>{driver.approval_status}</strong>
          {driver.submitted_at
            ? ` · submitted ${new Date(driver.submitted_at).toLocaleString()}`
            : ""}
        </p>

        <table>
          <tbody>
            <tr>
              <td>Licence number</td>
              <td>
                {driver.license_number ?? <span className="muted">—</span>}
              </td>
            </tr>
            <tr>
              <td>Licence expiry</td>
              <td>
                {driver.license_expiry ? (
                  driver.license_expiry.slice(0, 10)
                ) : (
                  <span className="muted">—</span>
                )}
              </td>
            </tr>
            <tr>
              <td>National ID</td>
              <td>{driver.national_id ?? <span className="muted">—</span>}</td>
            </tr>
            <tr>
              <td>Plate</td>
              <td>{driver.plate_number ?? <span className="muted">—</span>}</td>
            </tr>
            <tr>
              <td>Car</td>
              <td>{driver.car_model ?? <span className="muted">—</span>}</td>
            </tr>
          </tbody>
        </table>

        {present.length > 0 ? (
          <div
            style={{
              display: "grid",
              gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))",
              gap: 12,
              margin: "12px 0",
            }}
          >
            {present.map(([name, label]) => (
              <DocumentScan key={name} name={name as string} label={label} />
            ))}
          </div>
        ) : (
          <div className="muted" style={{ margin: "12px 0" }}>
            No scans on file — this profile predates document capture.
          </div>
        )}

        <input
          placeholder="Reason (required to reject or suspend)"
          value={reason}
          onChange={(e) => setReason(e.target.value)}
        />

        {error && <div className="error">{error}</div>}

        <div className="row-actions">
          <button disabled={busy} onClick={() => decide("approved")}>
            {busy ? "Saving…" : "Approve"}
          </button>
          <button
            type="button"
            className="danger"
            disabled={busy}
            onClick={() => decide("rejected")}
          >
            Reject
          </button>
          {driver.approval_status === "approved" && (
            <button
              type="button"
              className="danger"
              disabled={busy}
              onClick={() => decide("suspended")}
            >
              Suspend
            </button>
          )}
          <button type="button" className="secondary" onClick={onClose}>
            Close
          </button>
        </div>
      </div>
    </div>
  );
}

function DriverForm({
  initial,
  onClose,
  onSaved,
}: {
  initial: Driver | null;
  onClose: () => void;
  onSaved: () => void;
}) {
  const [form, setForm] = useState(
    initial
      ? {
          first_name: initial.first_name,
          last_name: initial.last_name,
          profile_image_url: initial.profile_image_url ?? "",
          car_image_url: initial.car_image_url ?? "",
          car_seats: initial.car_seats,
          rating: Number(initial.rating),
        }
      : { ...EMPTY },
  );
  const [error, setError] = useState<string | null>(null);
  const [busy, setBusy] = useState(false);

  const set = (key: keyof typeof form) => (e: { target: { value: string } }) =>
    setForm((f) => ({
      ...f,
      [key]:
        key === "car_seats" || key === "rating"
          ? Number(e.target.value)
          : e.target.value,
    }));

  const submit = async (e: FormEvent) => {
    e.preventDefault();
    setBusy(true);
    try {
      await api(initial ? `/admin/drivers/${initial.id}` : "/admin/drivers", {
        method: initial ? "PATCH" : "POST",
        body: JSON.stringify(form),
      });
      onSaved();
    } catch (err) {
      setError((err as Error).message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <form className="modal" onClick={(e) => e.stopPropagation()} onSubmit={submit}>
        <h3>{initial ? `Edit driver #${initial.id}` : "New driver"}</h3>
        <input placeholder="First name" value={form.first_name} onChange={set("first_name")} required />
        <input placeholder="Last name" value={form.last_name} onChange={set("last_name")} required />
        <input placeholder="Profile image URL" value={form.profile_image_url} onChange={set("profile_image_url")} />
        <input placeholder="Car image URL" value={form.car_image_url} onChange={set("car_image_url")} />
        <label>
          Seats{" "}
          <select value={form.car_seats} onChange={set("car_seats")}>
            {[2, 4, 6, 7].map((n) => (
              <option key={n} value={n}>
                {n}
              </option>
            ))}
          </select>
        </label>
        <input
          type="number"
          step="0.1"
          min="1"
          max="5"
          placeholder="Rating"
          value={form.rating}
          onChange={set("rating")}
        />
        {error && <div className="error">{error}</div>}
        <div className="row-actions">
          <button disabled={busy}>{busy ? "Saving…" : "Save"}</button>
          <button type="button" className="secondary" onClick={onClose}>
            Cancel
          </button>
        </div>
      </form>
    </div>
  );
}

// Pick exactly which rides a payment covers.
//
// Settling is an assertion about the real world — that cash was handed over,
// or a transfer was made — so the operator has to be able to say precisely
// which trips it accounts for. Everything is selected by default, because
// settling the whole balance is still the common case; unticking is the
// exception, not the workflow.
type UnsettledRide = {
  ride_id: number;
  amount_cents: number;
  fare_price: number;
  origin_address: string;
  destination_address: string;
  completed_at: string;
};

function SettlePicker({
  driver,
  side,
  onClose,
  onSettled,
}: {
  driver: Driver;
  side: "platform_fee" | "driver_payout";
  onClose: () => void;
  onSettled: () => void;
}) {
  const [rides, setRides] = useState<UnsettledRide[]>([]);
  const [picked, setPicked] = useState<Set<number>>(new Set());
  const [note, setNote] = useState("");
  const [loading, setLoading] = useState(true);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const collecting = side === "platform_fee";

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const res = await api<{ data: { rides: UnsettledRide[] } }>(
          `/admin/settle?driver_id=${driver.id}&side=${side}`,
        );
        if (cancelled) return;
        setRides(res.data.rides);
        setPicked(new Set(res.data.rides.map((r) => r.ride_id)));
      } catch (e) {
        if (!cancelled) setError((e as Error).message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [driver.id, side]);

  const toggle = (rideId: number) =>
    setPicked((prev) => {
      const next = new Set(prev);
      if (next.has(rideId)) next.delete(rideId);
      else next.add(rideId);
      return next;
    });

  const allPicked = rides.length > 0 && picked.size === rides.length;
  const total = rides
    .filter((r) => picked.has(r.ride_id))
    .reduce((sum, r) => sum + r.amount_cents, 0);

  const submit = async () => {
    if (picked.size === 0) return;
    setBusy(true);
    try {
      await api("/admin/settle", {
        method: "POST",
        body: JSON.stringify({
          side,
          // Ride ids, not driver_id: the server settles exactly these and
          // leaves the rest of the balance outstanding.
          ride_ids: [...picked],
          note: note.trim() || undefined,
        }),
      });
      onSettled();
    } catch (e) {
      setError((e as Error).message);
      setBusy(false);
    }
  };

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <h3>
          {collecting ? "Collect commission from" : "Pay out"}{" "}
          {driver.first_name} {driver.last_name}
        </h3>
        <p className="muted" style={{ marginTop: -6 }}>
          {collecting
            ? "Cash rides where this driver still owes the platform fee."
            : "Card rides where the platform still owes this driver."}
        </p>

        {error ? <p className="error">{error}</p> : null}

        {loading ? (
          <p className="muted">Loading rides…</p>
        ) : rides.length === 0 ? (
          <p className="muted">Nothing outstanding.</p>
        ) : (
          <>
            <label style={{ display: "block", margin: "8px 0" }}>
              <input
                type="checkbox"
                checked={allPicked}
                onChange={() =>
                  setPicked(
                    allPicked
                      ? new Set()
                      : new Set(rides.map((r) => r.ride_id)),
                  )
                }
              />{" "}
              Select all ({rides.length})
            </label>

            <div style={{ maxHeight: 260, overflowY: "auto" }}>
              <table>
                <thead>
                  <tr>
                    <th></th>
                    <th>Ride</th>
                    <th>Route</th>
                    <th>Fare</th>
                    <th>{collecting ? "Commission" : "Payout"}</th>
                  </tr>
                </thead>
                <tbody>
                  {rides.map((r) => (
                    <tr key={r.ride_id}>
                      <td>
                        <input
                          type="checkbox"
                          checked={picked.has(r.ride_id)}
                          onChange={() => toggle(r.ride_id)}
                        />
                      </td>
                      <td>#{r.ride_id}</td>
                      <td style={{ fontSize: 11 }}>
                        {r.origin_address} → {r.destination_address}
                        <div className="muted">
                          {new Date(r.completed_at).toLocaleDateString()}
                        </div>
                      </td>
                      <td>{money(r.fare_price)}</td>
                      <td>
                        <strong>{money(r.amount_cents)}</strong>
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>

            <input
              placeholder="Reference (transfer id, receipt no., 'cash in office')"
              value={note}
              onChange={(e) => setNote(e.target.value)}
            />

            <p>
              <strong>
                {picked.size} of {rides.length} ride
                {rides.length === 1 ? "" : "s"} · {money(total)}
              </strong>
            </p>
          </>
        )}

        <div className="row-actions">
          <button className="secondary" onClick={onClose} disabled={busy}>
            Cancel
          </button>
          <button
            onClick={submit}
            disabled={busy || picked.size === 0}
            title={
              picked.size === 0 ? "Select at least one ride" : undefined
            }
          >
            {busy
              ? "Recording…"
              : collecting
                ? `Mark ${money(total)} collected`
                : `Mark ${money(total)} paid`}
          </button>
        </div>
      </div>
    </div>
  );
}
