import type {
  Calories,
  CatalogCreateInput,
  CatalogEntity,
  CatalogKind,
  Progression,
  User,
  Workout,
  WorkoutItem,
  WorkoutSet,
  WorkoutSetInput,
} from "./types";

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "/api";

export type AuthState = {
  accessToken: string;
  user: User;
};

export class ApiError extends Error {
  status: number;

  constructor(message: string, status: number) {
    super(message);
    this.name = "ApiError";
    this.status = status;
  }
}

async function request<T>(path: string, options: RequestInit = {}, token?: string): Promise<T> {
  const headers = new Headers(options.headers);
  if (!(options.body instanceof FormData) && options.body !== undefined) {
    headers.set("Content-Type", "application/json");
  }
  if (token) {
    headers.set("Authorization", `Bearer ${token}`);
  }

  const response = await fetch(`${API_BASE_URL}${path}`, { ...options, headers });
  if (!response.ok) {
    const body = await response.json().catch(() => ({ detail: response.statusText }));
    const detail = typeof body.detail === "string" ? body.detail : JSON.stringify(body.detail);
    throw new ApiError(detail, response.status);
  }
  if (response.status === 204 || response.headers.get("content-length") === "0") {
    return undefined as T;
  }
  return response.json() as Promise<T>;
}

export const api = {
  async register(payload: { email: string; password: string; display_name: string }): Promise<AuthState> {
    const raw = await request<{ access_token: string; user: User }>(
      "/auth/register",
      { method: "POST", body: JSON.stringify(payload) },
    );
    return { accessToken: raw.access_token, user: raw.user };
  },
  async login(payload: { email: string; password: string }): Promise<AuthState> {
    const raw = await request<{ access_token: string; user: User }>(
      "/auth/login",
      { method: "POST", body: JSON.stringify(payload) },
    );
    return { accessToken: raw.access_token, user: raw.user };
  },
  me(token: string) {
    return request<User>("/me", {}, token);
  },
  equipment(token: string) {
    return request<CatalogEntity[]>("/catalog/equipment", {}, token);
  },
  exercises(token: string) {
    return request<CatalogEntity[]>("/catalog/exercises", {}, token);
  },
  activitySources(token: string, filters: { kind?: CatalogKind; category?: string; scope?: "all" | "builtin" | "mine"; search?: string } = {}) {
    const params = new URLSearchParams();
    Object.entries(filters).forEach(([key, value]) => {
      if (value) params.set(key, value);
    });
    const query = params.toString();
    return request<CatalogEntity[]>(`/catalog/activity-sources${query ? `?${query}` : ""}`, {}, token);
  },
  uploadImage(token: string, entityType: CatalogKind, file: File) {
    const form = new FormData();
    form.append("file", file);
    return request<{ image_s3_url: string; image_s3_key: string }>(
      `/media/images?entity_type=${entityType}`,
      { method: "POST", body: form },
      token,
    );
  },
  createEquipment(token: string, payload: Partial<CatalogEntity>) {
    return request<CatalogEntity>("/catalog/equipment", { method: "POST", body: JSON.stringify(payload) }, token);
  },
  createExercise(token: string, payload: Partial<CatalogEntity>) {
    return request<CatalogEntity>("/catalog/exercises", { method: "POST", body: JSON.stringify(payload) }, token);
  },
  createActivitySource(token: string, payload: CatalogCreateInput) {
    return request<CatalogEntity>("/catalog/activity-sources", { method: "POST", body: JSON.stringify(payload) }, token);
  },
  workouts(token: string) {
    return request<Workout[]>("/workouts", {}, token);
  },
  activeWorkout(token: string) {
    return request<Workout | null>("/workouts/active", {}, token);
  },
  getWorkout(token: string, workoutId: string) {
    return request<Workout>(`/workouts/${workoutId}`, {}, token);
  },
  createWorkout(token: string) {
    return request<Workout>("/workouts", { method: "POST", body: JSON.stringify({}) }, token);
  },
  updateWorkout(token: string, workoutId: string, payload: Partial<Workout>) {
    return request<Workout>(`/workouts/${workoutId}`, { method: "PATCH", body: JSON.stringify(payload) }, token);
  },
  finishWorkout(token: string, workoutId: string, notes?: string) {
    return request<Workout>(`/workouts/${workoutId}/finish`, {
      method: "POST",
      body: JSON.stringify(notes ? { notes } : {}),
    }, token);
  },
  discardWorkout(token: string, workoutId: string) {
    return request<Workout>(`/workouts/${workoutId}/discard`, { method: "POST" }, token);
  },
  addWorkoutItem(token: string, workoutId: string, payload: { activity_source_id?: string | null; exercise_id?: string | null; equipment_id?: string | null }) {
    return request<WorkoutItem>(`/workouts/${workoutId}/items`, { method: "POST", body: JSON.stringify(payload) }, token);
  },
  addWorkoutSet(token: string, itemId: string, payload: WorkoutSetInput) {
    return request<WorkoutSet>(`/workout-items/${itemId}/sets`, { method: "POST", body: JSON.stringify(payload) }, token);
  },
  addWorkoutSetBatch(token: string, itemId: string, payload: { sets: WorkoutSetInput[] }) {
    return request<WorkoutSet[]>(`/workout-items/${itemId}/sets/batch`, { method: "POST", body: JSON.stringify(payload) }, token);
  },
  updateWorkoutSet(token: string, setId: string, payload: Partial<WorkoutSetInput>) {
    return request<WorkoutSet>(`/workout-sets/${setId}`, { method: "PATCH", body: JSON.stringify(payload) }, token);
  },
  removeWorkoutItem(token: string, itemId: string) {
    return request<void>(`/workout-items/${itemId}`, { method: "DELETE" }, token);
  },
  removeWorkoutSet(token: string, itemId: string, setId: string) {
    return request<void>(`/workout-items/${itemId}/sets/${setId}`, { method: "DELETE" }, token);
  },
  progression(token: string, kind: CatalogKind, entityId?: string) {
    const params = new URLSearchParams({ kind });
    if (entityId) params.set("entity_id", entityId);
    return request<Progression>(`/analytics/progression?${params.toString()}`, {}, token);
  },
  calories(token: string) {
    return request<Calories>("/analytics/calories", {}, token);
  },
};
