"use client";

import { useCallback, useEffect, useMemo, useState } from "react";
import Link from "next/link";
import {
  api,
  Assignment,
  CommissioningBoard,
  DeviceEvent,
  DeviceProfile,
  Investor,
  Vehicle,
} from "@/lib/api";
import { CommissioningPanel } from "./CommissioningPanel";
import { FLEET_COLORS, FleetTone, fleetTone } from "@/components/FleetMap";
import { FleetMapPanel } from "./FleetMapPanel";
import { ImportModal } from "./ImportModal";
import { ModelsPanel } from "./ModelsPanel";
import { VehicleTypesPanel } from "./VehicleTypesPanel";
import { IotDevicesPanel } from "./IotDevicesPanel";
import { VehicleDetail } from "@/components/VehicleDetail";
import { useApi } from "@/hooks/useApi";
import { useVehicleTypes } from "@/lib/useVehicleTypes";
import { useLocale } from "@/i18n/LocaleProvider";
import {
  Badge,
  Button,
  Card,
  EmptyState,
  ErrorState,
  Field,
  Numeric,
  PageHeader,
  SegmentedControl,
  SkeletonTable,
  StatusBadge,
  Tone,
} from "@/components/ui";

/** Vehicle status → tone. Colour never carries the meaning alone; the label is always shown. */
const statusTone: Record<string, Tone> = {
  COMMISSIONING: "warning",
  AVAILABLE: "success",
  IN_USE: "info",
  LOW_BATTERY: "warning",
  MAINTENANCE: "warning",
  OFFLINE: "neutral",
};

type Tab = "vehicles" | "map" | "commissioning" | "models" | "types" | "devices" | "mqtt";

/** How often the commissioning queue refreshes while its tab is open. */
const POLL_MS = 5000;

export default function Page() {
  const { t } = useLocale();
  const [tab, setTab] = useState<Tab>("vehicles");
  const [importing, setImporting] = useState(false);
  const [busy, setBusy] = useState<string | null>(null);
  const vehicles = useApi<Vehicle[]>(api.listVehicles, []);
  const board = useApi<CommissioningBoard>(() => api.commissioningBoard(), []);
  // Loaded once here and handed to both catalogue panels, so the protocol list they offer and the
  // one the commissioning check judges against can never be different lists.
  const profiles = useApi<DeviceProfile[]>(() => api.deviceProfiles(), []);
  const investors = useApi<Investor[]>(api.listInvestors, []);
  const assignments = useApi<Assignment[]>(api.listAssignments, []);
  const [selected, setSelected] = useState<Vehicle | null>(null);

  const ownerOf = (vehicleId: string) =>
    assignments.data?.find((a) => a.scooterId === vehicleId) ?? null;
  const investorName = (investorId: string) =>
    investors.data?.find((i) => i.id === investorId)?.name ?? investorId.slice(0, 8);

  const refreshAll = () => {
    vehicles.reload();
    assignments.reload();
    investors.reload();
  };

  /**
   * The Vehicles tab shows vehicles released into service. Ones still being commissioned are
   * inbound stock, not fleet - counting them here would make every figure overstate what is
   * actually operable - so they get their own tab rather than being mixed in or hidden.
   */
  const inService = vehicles.data?.filter((v) => v.status !== "COMMISSIONING") ?? null;
  const awaiting = board.data?.total ?? 0;
  const readyCount = board.data?.ready ?? 0;

  const reloadBoard = board.reload;
  useEffect(() => {
    // Only while the queue is on screen: it is a live view for someone standing in a warehouse,
    // not something worth polling behind a tab nobody is looking at.
    if (tab !== "commissioning" || busy || importing) return;
    const id = setInterval(reloadBoard, POLL_MS);
    return () => clearInterval(id);
  }, [tab, busy, importing, reloadBoard]);

  const refreshBoth = () => {
    vehicles.reload();
    reloadBoard();
  };

  async function commissionAllReady() {
    if (!board.data) return;
    setBusy("all");
    try {
      // Sequential on purpose: each one is a real vehicle entering public service, and a burst of
      // parallel writes makes a partial failure much harder to explain afterwards.
      for (const v of board.data.vehicles.filter((x) => x.ready)) {
        await api.commissionVehicle(v.id);
      }
      refreshBoth();
    } finally {
      setBusy(null);
    }
  }

  return (
    <div>
      <PageHeader
        titleKey="nav.fleet"
        subtitle={
          inService ? (
            <>
              <Numeric>{inService.length}</Numeric> {t("fleet.inService")}
              {awaiting > 0 && (
                <>
                  {" · "}
                  <Numeric>{awaiting}</Numeric> {t("fleet.awaitingShort")}
                </>
              )}
            </>
          ) : undefined
        }
        actions={
          <>
            {tab === "commissioning" && readyCount > 0 && (
              <Button onClick={commissionAllReady} disabled={busy !== null}>
                {t("com.commissionAll")} ({readyCount})
              </Button>
            )}
            {(tab === "vehicles" || tab === "commissioning") && (
              <>
                <Link href="/fleet/connect">
                  <Button>{t("com.addOne")}</Button>
                </Link>
                {/* The one door in. Primary because adding vehicles is what this screen is for -
                    the tab bar below is a selector, not a competing action. */}
                <Button variant="primary" onClick={() => setImporting(true)}>
                  {t("com.import")}
                </Button>
              </>
            )}
          </>
        }
      />

      {/* The tab bar gets its own full-width row. Sharing the title's row with two buttons left
          it wrapping onto a second line with one tab stranded on it. */}
      <div className="mb-6">
        <SegmentedControl
          layout="scroll"
          value={tab}
          onChange={setTab}
          options={[
            { value: "vehicles", label: t("fleet.tabVehicles") },
            {
              value: "commissioning",
              // The count is the point of the tab: it is how a batch left in the pipeline
              // makes itself impossible to forget.
              label: awaiting > 0
                ? `${t("fleet.tabCommissioning")} · ${awaiting}`
                : t("fleet.tabCommissioning"),
            },
            { value: "map", label: t("fleet.tabMap") },
            { value: "models", label: t("fleet.tabModels") },
            { value: "types", label: t("fleet.tabTypes") },
            { value: "devices", label: t("fleet.tabDevices") },
            { value: "mqtt", label: t("fleet.tabMqtt") },
          ]}
        />
      </div>

      {tab === "vehicles" && inService && inService.length > 0 && (
        <FleetSummary vehicles={inService} />
      )}

      {tab === "vehicles" && (
        <VehiclesTable
          state={{ ...vehicles, data: inService }}
          ownerOf={ownerOf}
          investorName={investorName}
          onSelect={setSelected}
        />
      )}

      {tab === "map" && (
        <FleetMapPanel
          // The map shows what is out on the street, so vehicles still being commissioned are
          // excluded here for the same reason they are excluded from the Vehicles tab.
          vehicles={inService ?? []}
          selectedId={selected?.id ?? null}
          onSelect={setSelected}
          onRefresh={vehicles.reload}
        />
      )}

      {tab === "commissioning" &&
        (board.error ? (
          <ErrorState onRetry={reloadBoard} />
        ) : board.data ? (
          <CommissioningPanel
            board={board.data}
            reload={refreshBoth}
            busy={busy}
            setBusy={setBusy}
            onImport={() => setImporting(true)}
          />
        ) : (
          <SkeletonTable rows={5} />
        ))}

      {tab === "models" && <ModelsPanel profiles={profiles.data ?? []} />}

      {tab === "types" && <VehicleTypesPanel />}

      {tab === "devices" && <IotDevicesPanel profiles={profiles.data ?? []} />}

      {tab === "mqtt" && <MqttMonitor />}

      <ImportModal
        open={importing}
        onClose={() => setImporting(false)}
        onImported={refreshBoth}
      />

      <VehicleDetail
        vehicle={selected}
        investors={investors.data ?? []}
        assignment={selected ? ownerOf(selected.id) : null}
        onClose={() => setSelected(null)}
        onChanged={() => {
          refreshAll();
          // Re-read the row so the sheet reflects what was just saved.
          if (selected) {
            api.getVehicle(selected.id).then(setSelected).catch(() => setSelected(null));
          }
        }}
      />
    </div>
  );
}

/* ------------------------------------------------------------------ vehicles */

function VehiclesTable({
  state,
  ownerOf,
  investorName,
  onSelect,
}: {
  state: {
    data: Vehicle[] | null;
    loading: boolean;
    error: unknown;
    reload: () => void;
  };
  ownerOf: (vehicleId: string) => Assignment | null;
  investorName: (investorId: string) => string;
  onSelect: (v: Vehicle) => void;
}) {
  const { t } = useLocale();
  const { label: typeLabel } = useVehicleTypes();

  if (state.loading) return <SkeletonTable rows={6} />;
  if (state.error) return <ErrorState onRetry={state.reload} />;
  if (!state.data?.length) {
    return (
      <EmptyState
        icon={<span className="text-xl">🛴</span>}
        title={t("fleet.emptyTitle")}
        description={t("fleet.addHint")}
      />
    );
  }

  return (
    <div className="card overflow-x-auto">
      <table className="w-full min-w-[960px] text-sm">
        <thead>
          <tr className="border-b border-line bg-surface-raised/60">
            <Th>{t("fleet.colCode")}</Th>
            <Th>{t("fleet.colType")}</Th>
            <Th>{t("fleet.colImei")}</Th>
            <Th>{t("fleet.colBattery")}</Th>
            <Th>{t("fleet.colStatus")}</Th>
            <Th>{t("fleet.colOwner")}</Th>
            <Th>{t("fleet.colSeen")}</Th>
          </tr>
        </thead>
        <tbody>
          {state.data.map((v) => (
            <tr
              key={v.id}
              onClick={() => onSelect(v)}
              className="group cursor-pointer border-b border-line transition last:border-b-0 hover:bg-surface-raised"
            >
              <td className="px-4 py-3.5">
                <div className="flex items-center gap-3">
                  {/* The status colour rides next to the code as well as in its own column: on a
                      long list the eye scans the left edge, not the middle. */}
                  <span
                    className="h-2 w-2 shrink-0 rounded-full"
                    style={{ backgroundColor: FLEET_COLORS[fleetTone(v)] }}
                    aria-hidden
                  />
                  <div className="min-w-0">
                    <div className="font-medium">
                      <Numeric>{v.qrCode}</Numeric>
                    </div>
                    {v.model && <div className="truncate text-xs text-muted">{v.model}</div>}
                  </div>
                </div>
              </td>
              <td className="px-4 py-3.5">
                <Badge>{typeLabel(v.vehicleType)}</Badge>
              </td>
              <td className="px-4 py-3.5">
                {/* IMEI is the device's identity on every OMNI MQTT topic. */}
                {v.imei ? (
                  <Numeric className="text-xs text-muted">{v.imei}</Numeric>
                ) : (
                  <span className="text-xs text-muted-dim">{t("fleet.noDevice")}</span>
                )}
              </td>
              <td className="px-4 py-3.5">
                <BatteryCell percent={v.batteryPercent} />
              </td>
              <td className="px-4 py-3.5">
                <StatusBadge tone={statusTone[v.status] ?? "neutral"}>
                  {t(`status.${v.status}` as never)}
                </StatusBadge>
              </td>
              <td className="px-4 py-3.5 text-sm">
                {(() => {
                  const owner = ownerOf(v.id);
                  return owner ? (
                    investorName(owner.investorId)
                  ) : (
                    <span className="text-muted">{t("vehicle.ownerPlatform")}</span>
                  );
                })()}
              </td>
              <td className="px-4 py-3.5 text-xs text-muted">
                {v.lastSeenAt ? (
                  (() => {
                    const rel = relativeTime(v.lastSeenAt, t("fleet.never"));
                    return (
                      <span title={rel.exact}>
                        <Numeric>{rel.label}</Numeric>
                      </span>
                    );
                  })()
                ) : (
                  <span className="text-muted-dim">{t("fleet.never")}</span>
                )}
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function Th({ children }: { children: React.ReactNode }) {
  return (
    <th className="px-4 py-3 text-start text-[11px] font-semibold uppercase tracking-wider text-muted">
      {children}
    </th>
  );
}

/* ---------------------------------------------------------------------- mqtt */

/**
 * Live OMNI MQTT traffic.
 *
 * Polled, not streamed: the bridge exposes a rolling in-memory window, and a 3s poll is enough to
 * watch a fleet without holding a connection open per operator. Pausing stops the poll so a frame
 * you are reading doesn't scroll away.
 */
function MqttMonitor() {
  const { t } = useLocale();
  const [events, setEvents] = useState<DeviceEvent[] | null>(null);
  const [error, setError] = useState(false);
  const [filter, setFilter] = useState("");
  const [live, setLive] = useState(true);

  const load = useCallback(async () => {
    try {
      setEvents(await api.mqttEvents(filter || undefined, 100));
      setError(false);
    } catch {
      setError(true);
    }
  }, [filter]);

  useEffect(() => {
    load();
    if (!live) return;
    const id = setInterval(load, 3000);
    return () => clearInterval(id);
  }, [load, live]);

  return (
    <div className="space-y-4">
      <Card>
        <div className="flex flex-wrap items-end gap-3">
          <Field
            label={t("fleet.filterImei")}
            value={filter}
            onChange={(e) => setFilter(e.target.value)}
            placeholder="864475045545158"
            className="min-w-[240px] flex-1"
          />
          <Button onClick={() => setLive((v) => !v)}>
            {live ? t("fleet.pause") : t("fleet.resume")}
          </Button>
        </div>
        <p className="mt-3 text-xs text-muted">{t("fleet.mqttHint")}</p>
      </Card>

      {error ? (
        <ErrorState onRetry={load} />
      ) : events === null ? (
        <SkeletonTable rows={8} />
      ) : events.length === 0 ? (
        <EmptyState
          icon={<span className="text-xl">📡</span>}
          title={t("fleet.mqttEmptyTitle")}
          description={t("fleet.mqttEmptyBody")}
        />
      ) : (
        <div className="card divide-y divide-line">
          {events.map((e, i) => (
            <div key={`${e.at}-${i}`} className="flex flex-col gap-1 p-3">
              <div className="flex flex-wrap items-center gap-2">
                <StatusBadge tone={e.direction === "IN" ? "info" : "brand"}>
                  {e.direction}
                </StatusBadge>
                <span className="font-mono text-xs">{e.name}</span>
                <span className="font-mono text-[11px] text-muted-dim">{e.topic}</span>
                <span className="ms-auto text-[11px] text-muted">
                  {new Date(e.at).toLocaleTimeString()}
                </span>
              </div>
              <pre className="overflow-x-auto rounded-chip bg-background p-2 font-mono text-[11px] text-muted">
                {e.payload}
              </pre>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

/* -------------------------------------------------------------------- summary */

/**
 * The five numbers an operator wants before reading any rows.
 *
 * <p>Uses the same `fleetTone` the live map colours dots with, so the strip and the map can never
 * disagree about how many vehicles are in trouble. Deliberately not clickable: the Vehicles tab
 * has no filter to drive, and a tile that looks pressable but does nothing is worse than a plain
 * one. Filtering lives on the map tab, where it has somewhere to go.
 */
function FleetSummary({ vehicles }: { vehicles: Vehicle[] }) {
  const { t } = useLocale();
  const counts = useMemo(() => {
    const acc: Record<FleetTone, number> = {
      available: 0,
      inUse: 0,
      lowBattery: 0,
      maintenance: 0,
      offline: 0,
    };
    vehicles.forEach((v) => (acc[fleetTone(v)] += 1));
    return acc;
  }, [vehicles]);

  const cells: { tone: FleetTone; value: number }[] = [
    { tone: "available", value: counts.available },
    { tone: "inUse", value: counts.inUse },
    { tone: "lowBattery", value: counts.lowBattery },
    { tone: "maintenance", value: counts.maintenance },
    { tone: "offline", value: counts.offline },
  ];

  return (
    <div className="mb-4 grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-5">
      {cells.map((c) => (
        <div key={c.tone} className="card flex items-center justify-between gap-3 p-4">
          {/* Label left, figure right, so the tile fills its width instead of leaving a dead
              third. A dot rather than a tinted panel: five tinted cards in a row would fight each
              other and the one brand-yellow action on the screen. */}
          <div className="flex min-w-0 items-center gap-2.5">
            <span
              className="h-2.5 w-2.5 shrink-0 rounded-full"
              style={{ backgroundColor: FLEET_COLORS[c.tone] }}
              aria-hidden
            />
            <span className="truncate text-[13px] text-muted">
              {t(`fleetMap.tone.${c.tone}` as never)}
            </span>
          </div>
          <div
            className={`text-2xl font-semibold leading-none ${
              c.value === 0 ? "text-muted-dim" : ""
            }`}
          >
            <Numeric>{c.value}</Numeric>
          </div>
        </div>
      ))}
    </div>
  );
}

/** A compact battery gauge. The bar is the thing you scan; the number is the detail. */
function BatteryCell({ percent }: { percent: number }) {
  const tone = percent < 20 ? "danger" : percent < 40 ? "warning" : "success";
  const barClass =
    tone === "danger" ? "bg-danger" : tone === "warning" ? "bg-warning" : "bg-success";
  return (
    <div className="flex items-center gap-2">
      <div className="h-1.5 w-12 overflow-hidden rounded-full bg-line">
        <div
          className={`h-full rounded-full ${barClass}`}
          style={{ width: `${Math.max(0, Math.min(100, percent))}%` }}
        />
      </div>
      <Numeric className="text-xs text-muted">{percent}%</Numeric>
    </div>
  );
}

/**
 * "3 min ago" rather than a timestamp.
 *
 * <p>On a fleet list the question is always how stale a reading is, and nobody computes that from
 * an absolute date at a glance. The exact time stays in the tooltip for when it matters.
 */
function relativeTime(iso: string, never: string): { label: string; exact: string } {
  const then = Date.parse(iso);
  if (Number.isNaN(then)) return { label: never, exact: iso };
  const seconds = Math.max(0, Math.round((Date.now() - then) / 1000));
  const exact = new Date(then).toLocaleString();
  if (seconds < 60) return { label: `${seconds}s`, exact };
  const minutes = Math.round(seconds / 60);
  if (minutes < 60) return { label: `${minutes}m`, exact };
  const hours = Math.round(minutes / 60);
  if (hours < 24) return { label: `${hours}h`, exact };
  return { label: `${Math.round(hours / 24)}d`, exact };
}
