import type {
  AudioAsset,
  AudioAssetImportCreate,
  AudioAssetUpdate,
  AudioClip,
  AudioClipUpdate,
  GameAssignment,
  GamePrepResponse,
  SessionResponse,
  TeamSnapTokenResponse,
} from "./types";
import { cachedJsonRequest, clearOfflineCache } from "../lib/offlineCache";

export const API_BASE = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000";

type UploadAssetPayload = {
  teamId: string;
  playerId: string;
  title: string;
  file: File;
  onProcessingStart?: () => void;
  onUploadProgress?: (percent: number) => void;
};

async function request<T>(path: string, init?: RequestInit): Promise<T> {
  const headers = new Headers(init?.headers);
  if (init?.body != null && !headers.has("Content-Type")) {
    headers.set("Content-Type", "application/json");
  }

  const response = await fetch(`${API_BASE}${path}`, {
    credentials: "include",
    ...init,
    headers,
  });

  if (!response.ok) {
    const detail = await response.text();
    throw new Error(detail || `Request failed: ${response.status}`);
  }

  return response.json() as Promise<T>;
}

export const api = {
  getSession: () =>
    cachedJsonRequest<SessionResponse>(
      ["session"],
      `${API_BASE}/auth/session`,
      undefined,
      {
        onUnauthorized: () => {
          clearOfflineCache();
          return { authenticated: false, is_admin: false };
        },
      },
    ),
  startTeamSnap: (returnTo: string) =>
    request<{ authorize_url: string; state: string }>(`/auth/teamsnap/start?return_to=${encodeURIComponent(returnTo)}`),
  getTeamSnapToken: () => request<TeamSnapTokenResponse>("/auth/teamsnap/token", { method: "POST" }),
  adminLogin: (payload: { username: string; password: string }) =>
    request<SessionResponse>("/auth/admin/login", { method: "POST", body: JSON.stringify(payload) }),
  logout: () => request<{ ok: boolean }>("/auth/logout", { method: "POST" }),
  updateWalkupSessionSelection: (payload: { external_team_id: string; external_player_id: string }) =>
    request<SessionResponse>("/auth/session/walkup", { method: "POST", body: JSON.stringify(payload) }),
  listAssets: (teamId: string, playerId?: string) =>
    cachedJsonRequest<AudioAsset[]>(
      ["assets", teamId, playerId ?? ""],
      `${API_BASE}/media/assets?external_team_id=${encodeURIComponent(teamId)}${
        playerId ? `&owner_external_player_id=${encodeURIComponent(playerId)}` : ""
      }`,
    ),
  updateAsset: (assetId: number, payload: AudioAssetUpdate, ownerExternalPlayerId?: string) =>
    request<AudioAsset>(
      `/media/assets/${assetId}${ownerExternalPlayerId ? `?owner_external_player_id=${encodeURIComponent(ownerExternalPlayerId)}` : ""}`,
      { method: "PATCH", body: JSON.stringify(payload) },
    ),
  uploadAsset: async (payload: UploadAssetPayload) => {
    const formData = new FormData();
    formData.set("external_team_id", payload.teamId);
    formData.set("owner_external_player_id", payload.playerId);
    formData.set("title", payload.title);
    formData.set("file", payload.file);

    return new Promise<AudioAsset>((resolve, reject) => {
      const request = new XMLHttpRequest();
      request.open("POST", `${API_BASE}/media/uploads`);
      request.withCredentials = true;

      request.upload.addEventListener("progress", (event) => {
        if (!event.lengthComputable || event.total <= 0) {
          return;
        }

        payload.onUploadProgress?.(Math.min(100, Math.round((event.loaded / event.total) * 100)));
      });

      request.upload.addEventListener("load", () => {
        payload.onUploadProgress?.(100);
        payload.onProcessingStart?.();
      });

      request.addEventListener("load", () => {
        if (request.status < 200 || request.status >= 300) {
          reject(new Error(request.responseText || `Request failed: ${request.status}`));
          return;
        }

        resolve(JSON.parse(request.responseText) as AudioAsset);
      });

      request.addEventListener("error", () => reject(new Error("Upload failed. Check the connection and try again.")));
      request.addEventListener("abort", () => reject(new Error("Upload was cancelled.")));
      request.send(formData);
    });
  },
  importAssetFromUrl: (payload: AudioAssetImportCreate) =>
    request<AudioAsset>("/media/imports", { method: "POST", body: JSON.stringify(payload) }),
  deleteAsset: async (assetId: number, ownerExternalPlayerId?: string) => {
    const response = await fetch(
      `${API_BASE}/media/assets/${assetId}${ownerExternalPlayerId ? `?owner_external_player_id=${encodeURIComponent(ownerExternalPlayerId)}` : ""}`,
      {
        method: "DELETE",
        credentials: "include",
      },
    );
    if (!response.ok) {
      throw new Error(await response.text());
    }
  },
  updateClip: (clipId: number, payload: AudioClipUpdate, ownerExternalPlayerId?: string) =>
    request<AudioClip>(
      `/media/clips/${clipId}${ownerExternalPlayerId ? `?owner_external_player_id=${encodeURIComponent(ownerExternalPlayerId)}` : ""}`,
      { method: "PATCH", body: JSON.stringify(payload) },
    ),
  deleteClip: async (clipId: number, ownerExternalPlayerId?: string) => {
    const response = await fetch(
      `${API_BASE}/media/clips/${clipId}${ownerExternalPlayerId ? `?owner_external_player_id=${encodeURIComponent(ownerExternalPlayerId)}` : ""}`,
      {
        method: "DELETE",
        credentials: "include",
      },
    );
    if (!response.ok) {
      throw new Error(await response.text());
    }
  },
  listClips: (teamId: string, playerId?: string, includeHidden = false) =>
    cachedJsonRequest<AudioClip[]>(
      ["clips", teamId, playerId ?? "", includeHidden ? "all" : "visible"],
      `${API_BASE}/media/clips?external_team_id=${encodeURIComponent(teamId)}${
        playerId ? `&owner_external_player_id=${encodeURIComponent(playerId)}` : ""
      }${includeHidden ? "&include_hidden=true" : ""}`,
    ),
  createClip: (payload: {
    asset_id: number;
    external_team_id: string;
    owner_external_player_id: string;
    label: string;
    start_ms: number;
    end_ms: number;
  }) =>
    request<AudioClip>("/media/clips", { method: "POST", body: JSON.stringify(payload) }),
  reorderClips: async (payload: { external_team_id: string; owner_external_player_id: string; clip_ids: number[] }) => {
    const response = await fetch(`${API_BASE}/media/clips/reorder`, {
      method: "POST",
      credentials: "include",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(payload),
    });
    if (!response.ok) {
      throw new Error(await response.text());
    }
  },
  listAssignments: (gameId: string, playerId?: string) =>
    cachedJsonRequest<GameAssignment[]>(
      ["assignments", gameId, playerId ?? ""],
      `${API_BASE}/games/${encodeURIComponent(gameId)}/assignments${
        playerId ? `?external_player_id=${encodeURIComponent(playerId)}` : ""
      }`,
    ),
  listPins: (playerId: string) =>
    cachedJsonRequest<GameAssignment[]>(["pins", playerId], `${API_BASE}/games/pins?external_player_id=${encodeURIComponent(playerId)}`),
  createAssignment: (
    gameId: string,
    payload: {
      external_team_id: string;
      external_player_id: string;
      clip_id: number;
      batting_slot?: number | null;
      status: string;
    },
  ) =>
    request<GameAssignment>(`/games/${encodeURIComponent(gameId)}/assignments`, {
      method: "POST",
      body: JSON.stringify(payload),
    }),
  deleteAssignment: (gameId: string, assignmentId: number, playerId?: string) =>
    fetch(
      `${API_BASE}/games/${encodeURIComponent(gameId)}/assignments/${assignmentId}${
        playerId ? `?external_player_id=${encodeURIComponent(playerId)}` : ""
      }`,
      {
        method: "DELETE",
        credentials: "include",
      },
    ).then(async (response) => {
      if (!response.ok) {
        throw new Error(await response.text());
      }
    }),
  prepareGame: (gameId: string) =>
    cachedJsonRequest<GamePrepResponse>(["prep", gameId], `${API_BASE}/games/${encodeURIComponent(gameId)}/prep`),
};
