export type User = { id: number; username: string; role: "admin" };

export type AuthState = {
  setupRequired: boolean;
  user: User | null;
  csrfToken: string;
};

export type Availability<T> =
  | { available: true; value: T }
  | { available: false; reason: string };

export type HeadUnitStatus =
  | {
      available: true;
      value: {
        state: "connected";
        identity: {
          manufacturer: Availability<string>;
          model: Availability<string>;
          androidVersion: Availability<string>;
          apiLevel: Availability<number>;
          buildId: Availability<string>;
          abi: Availability<string>;
        };
        display: {
          size: Availability<{ widthPixels: number; heightPixels: number }>;
          densityDpi: Availability<number>;
        };
      };
    }
  | {
      available: false;
      state: "disabled" | "unavailable" | "offline" | "unauthorized" | "ambiguous";
      reason: string;
    };

export type SystemStatus = {
  collectedAt: string;
  hostname: string;
  uptimeSeconds: number;
  operatingSystem: string;
  cpu: {
    model: string;
    loadAverage: number[];
    temperatureCelsius: Availability<number>;
  };
  memory: { totalBytes: number; usedBytes: number; availableBytes: number };
  disk: Availability<{ totalBytes: number; usedBytes: number; availableBytes: number; mount: string }>;
  network: {
    interfaces: Array<{ name: string; address: string; family: string }>;
    interfaceReason: string | null;
    wifi: Availability<string>;
  };
  service: { state: "healthy"; processUptimeSeconds: number };
  headUnit: HeadUnitStatus;
};

export type UpdateStatus = {
  state: "idle" | "checking" | "available" | "building" | "current" | "success" | "failed";
  message: string;
  fromRevision: string | null;
  toRevision: string | null;
  updatedAt: string;
  installedRevision: string | null;
  supported: boolean;
};

export type NetworkStatus = {
  supported: boolean;
  enabled: boolean;
  mode: "disabled" | "starting" | "dual_radio" | "built_in_upstream" | "probing_built_in" | "offline_hotspot" | "degraded" | "error";
  message: string;
  updatedAt: string;
  lastTransitionAt: string;
  probeDeadline: string | null;
  lastFailure: string | null;
  connectivity: "full" | "limited" | "portal" | "none" | "unknown";
  hotspotSsid: string | null;
  hotspot: NetworkAdapterStatus;
  upstream: NetworkAdapterStatus;
};

export type NetworkAdapterStatus = {
  interface: string;
  available: boolean;
  connected: boolean;
  connection: string | null;
  address: string | null;
};

export type NetworkActivity = {
  supported: boolean;
  collectedAt: string;
  clients: Array<{ ipAddress: string; macAddress: string; hostname: string | null; leaseExpiresAt: string | null; state: string; connected: boolean }>;
  dnsQueries: Array<{ timestamp: string; clientAddress: string; type: string; name: string }>;
  flows: Array<{ protocol: "tcp" | "udp"; state: string; sourceAddress: string; sourcePort: number; destinationAddress: string; destinationPort: number; packets: number | null; bytes: number | null }>;
  dnsAvailable: boolean;
  flowsAvailable: boolean;
  messages: string[];
};

export type InstalledAdbPackage = {
  packageName: string;
  apkPath: string;
  system: boolean;
};

export type AdbTarget = { type: "component" | "package"; value: string };

export type AdbShortcut = AdbTarget & {
  id: number;
  name: string;
  createdAt: string;
};

export type MobileDevice = {
  id: string;
  name: string;
  createdAt: string;
  lastSeenAt: string;
  revokedAt: string | null;
};

export type BluetoothStatus = {
  supported: boolean;
  powered: boolean;
  discoverable: boolean;
  pairable: boolean;
  message: string;
};

export type TripSummary = {
  id: number;
  deviceId: string;
  deviceName: string;
  startedAt: number;
  endedAt: number | null;
  active: boolean;
  durationSeconds: number;
  movingSeconds: number;
  distanceKm: number;
  pointCount: number;
  averageSpeedKph: number | null;
  peakSpeedKph: number | null;
  vehicleBatteryStartPercent: number | null;
  vehicleBatteryEndPercent: number | null;
  batteryConsumedPercent: number | null;
  energyConsumedKwh: number | null;
  phoneBatteryStartPercent: number | null;
  phoneBatteryEndPercent: number | null;
  start: { latitude: number; longitude: number };
  end: { latitude: number; longitude: number };
};

export type TripPoint = {
  capturedAt: number;
  latitude: number;
  longitude: number;
  accuracyMeters: number;
  altitudeMeters: number | null;
  speedKph: number | null;
  bearingDegrees: number | null;
  vehicleBatteryPercent: number | null;
};

export type TripPin = {
  type: "start" | "end" | "peak-speed";
  latitude: number;
  longitude: number;
  capturedAt: number | null;
  speedKph?: number | null;
};

export type TripDetail = TripSummary & { points: TripPoint[]; pins: TripPin[] };

export type CarTelemetry = {
  available: true;
  collectedAt: string;
  speedKph: Availability<number>;
  gear: Availability<string>;
  wheelAngleDegrees: Availability<number>;
  batteryPercent: Availability<number>;
  rangeKm: Availability<number>;
  batteryVoltage: Availability<number>;
  totalConsumptionKwh: Availability<number>;
  chargingStatus: Availability<number>;
};

export type TelemetryPeriod = "24h" | "7d" | "30d" | "1y" | "all";

export type TelemetryHistoryPoint = {
  timestamp: number;
  speedKph: number | null;
  peakSpeedKph: number | null;
  batteryPercent: number | null;
  rangeKm: number | null;
  chargingPowerKw: number | null;
};

export type CarStatistics = {
  latest: {
    recordedAt: number;
    speedKph: number | null;
    wheelAngleDegrees: number | null;
    batteryPercent: number | null;
    rangeKm: number | null;
    batteryVoltage: number | null;
    totalConsumptionKwh: number | null;
    chargingStatus: number | null;
    chargingPowerKw: number | null;
  } | null;
  records: {
    peakSpeedKph: number | null;
    peakSpeedAt: number | null;
    peakChargingPowerKw: number | null;
    peakChargingPowerAt: number | null;
    maxObservedRangeKm: number | null;
    maxObservedRangeAt: number | null;
  };
  lifetime: {
    firstSampleAt: number | null;
    lastSampleAt: number | null;
    sampleCount: number;
    trackedDistanceKm: number;
    drivingSeconds: number;
    chargingSeconds: number;
    chargeSessions: number;
    socUsedPercent: number;
    averageMovingSpeedKph: number | null;
    ownRangeEstimateKm: number | null;
  };
  storage: {
    rawSamples: number;
    hourlyRollups: number;
    oldestRawAt: number | null;
    oldestRollupAt: number | null;
    estimatedBytes: number;
    rawRetentionDays: number;
    rollupRetentionYears: number;
    rawSampleCap: number;
  };
};

type ErrorResponse = { error?: { code?: string; message?: string } };

export class ApiError extends Error {
  constructor(
    message: string,
    public readonly status: number,
    public readonly code: string
  ) {
    super(message);
  }
}

async function parseResponse<T>(response: Response): Promise<T> {
  const data = (await response.json().catch(() => ({}))) as T & ErrorResponse;
  if (!response.ok) {
    throw new ApiError(data.error?.message ?? "The request failed", response.status, data.error?.code ?? "REQUEST_FAILED");
  }
  return data;
}

export async function getAuthState(): Promise<AuthState> {
  return parseResponse<AuthState>(await fetch("/api/auth/state", { credentials: "same-origin" }));
}

export async function post<T>(path: string, body: unknown, csrfToken: string): Promise<T> {
  return parseResponse<T>(
    await fetch(path, {
      method: "POST",
      credentials: "same-origin",
      headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken },
      body: JSON.stringify(body)
    })
  );
}

export async function getStatus(): Promise<SystemStatus> {
  return parseResponse<SystemStatus>(await fetch("/api/status", { credentials: "same-origin" }));
}

export async function getUpdateStatus(): Promise<UpdateStatus> {
  return parseResponse<UpdateStatus>(await fetch("/api/system/update", { credentials: "same-origin" }));
}

export async function getNetworkStatus(): Promise<NetworkStatus> {
  return parseResponse<NetworkStatus>(await fetch("/api/network", { credentials: "same-origin" }));
}

export async function getNetworkActivity(): Promise<NetworkActivity> {
  return parseResponse<NetworkActivity>(await fetch("/api/network/activity", { credentials: "same-origin" }));
}

export async function getAdbPackages(): Promise<InstalledAdbPackage[]> {
  const response = await parseResponse<{ packages: InstalledAdbPackage[] }>(
    await fetch("/api/adb/packages", { credentials: "same-origin" })
  );
  return response.packages;
}

export async function getAdbShortcuts(): Promise<AdbShortcut[]> {
  const response = await parseResponse<{ shortcuts: AdbShortcut[] }>(
    await fetch("/api/adb/shortcuts", { credentials: "same-origin" })
  );
  return response.shortcuts;
}

export async function getMobileDevices(): Promise<MobileDevice[]> {
  const response = await parseResponse<{ devices: MobileDevice[] }>(
    await fetch("/api/mobile/devices", { credentials: "same-origin" })
  );
  return response.devices;
}

export async function getBluetoothStatus(): Promise<BluetoothStatus> {
  return parseResponse<BluetoothStatus>(await fetch("/api/mobile/bluetooth", { credentials: "same-origin" }));
}

export async function getTrips(): Promise<TripSummary[]> {
  const response = await parseResponse<{ trips: TripSummary[] }>(
    await fetch("/api/trips", { credentials: "same-origin" })
  );
  return response.trips;
}

export async function getTrip(id: number): Promise<TripDetail> {
  return parseResponse<TripDetail>(await fetch(`/api/trips/${id}`, { credentials: "same-origin" }));
}

export async function revokeMobileDevice(id: string, csrfToken: string): Promise<void> {
  await parseResponse(await fetch(`/api/mobile/devices/${encodeURIComponent(id)}`, {
    method: "DELETE",
    credentials: "same-origin",
    headers: { "X-CSRF-Token": csrfToken }
  }));
}

export async function uploadApk(file: File, csrfToken: string): Promise<void> {
  await parseResponse(
    await fetch("/api/adb/install", {
      method: "POST",
      credentials: "same-origin",
      headers: {
        "Content-Type": "application/vnd.android.package-archive",
        "X-CSRF-Token": csrfToken
      },
      body: file
    })
  );
}

export async function deleteAdbShortcut(id: number, csrfToken: string): Promise<void> {
  await parseResponse(
    await fetch(`/api/adb/shortcuts/${id}`, {
      method: "DELETE",
      credentials: "same-origin",
      headers: { "X-CSRF-Token": csrfToken }
    })
  );
}

export async function getCarTelemetry(): Promise<CarTelemetry> {
  return parseResponse<CarTelemetry>(await fetch("/api/car/telemetry", { credentials: "same-origin" }));
}

export async function getCarHistory(period: TelemetryPeriod): Promise<{ period: TelemetryPeriod; points: TelemetryHistoryPoint[] }> {
  return parseResponse(await fetch(`/api/car/history?period=${period}`, { credentials: "same-origin" }));
}

export async function getCarStatistics(): Promise<CarStatistics> {
  return parseResponse<CarStatistics>(await fetch("/api/car/statistics", { credentials: "same-origin" }));
}

export type ClearTelemetryMode = "history" | "all";

export async function clearCarStatistics(mode: ClearTelemetryMode, csrfToken: string): Promise<void> {
  await post("/api/car/history/clear", { mode }, csrfToken);
}
