import type {
  CalendarEvent,
  EventResponse,
  EventStatus,
  EventType,
  EventVisibility,
  Room,
  RoomMember,
  RoomPermissions,
  RoomRole,
  SignupStatus,
  User,
} from "./types"

export const API_BASE_URL =
  process.env.NEXT_PUBLIC_API_BASE_URL?.replace(/\/$/, "") || "https://ben.de-roo.org/api"

const TOKEN_KEY = "calendar_token"

export function getToken(): string | null {
  if (typeof window === "undefined") return null
  return window.localStorage.getItem(TOKEN_KEY)
}

export function setToken(token: string) {
  window.localStorage.setItem(TOKEN_KEY, token)
}

export function clearToken() {
  window.localStorage.removeItem(TOKEN_KEY)
}

export class ApiError extends Error {
  status: number
  constructor(message: string, status: number) {
    super(message)
    this.status = status
    this.name = "ApiError"
  }
}

interface RequestOptions {
  method?: string
  body?: unknown
  auth?: boolean
  query?: Record<string, string | number | undefined>
}

async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {
  const { method = "GET", body, auth = true, query } = options

  let url = `${API_BASE_URL}${path}`
  if (query) {
    const params = new URLSearchParams()
    for (const [key, value] of Object.entries(query)) {
      if (value !== undefined && value !== null && value !== "") {
        params.append(key, String(value))
      }
    }
    const qs = params.toString()
    if (qs) url += `?${qs}`
  }

  const headers: Record<string, string> = {}
  if (body !== undefined) headers["Content-Type"] = "application/json"

  if (auth) {
    const token = getToken()
    if (token) headers["Authorization"] = `Bearer ${token}`
  }

  let res: Response
  try {
    res = await fetch(url, {
      method,
      headers,
      body: body !== undefined ? JSON.stringify(body) : undefined,
    })
  } catch (err) {
    throw new ApiError(
      "Could not reach the server. Check your connection and the API URL.",
      0,
    )
  }

  if (res.status === 401) {
    clearToken()
    throw new ApiError("Your session has expired. Please sign in again.", 401)
  }

  let data: any = null
  const text = await res.text()
  if (text) {
    try {
      data = JSON.parse(text)
    } catch {
      data = text
    }
  }

  if (!res.ok) {
    const detail =
      (data && typeof data === "object" && (data.detail || data.message)) ||
      (typeof data === "string" ? data : null) ||
      `Request failed (${res.status})`
    throw new ApiError(
      Array.isArray(detail) ? detail.map((d: any) => d.msg).join(", ") : String(detail),
      res.status,
    )
  }

  return data as T
}

// ---------- Auth ----------

export async function login(username: string, password: string) {
  const res = await request<{
    status: string
    access_token?: string
    token_type?: string
  }>("/login", { method: "POST", body: { username, password }, auth: false })

  if (res.status !== "success" || !res.access_token) {
    throw new ApiError("Invalid username or password.", 401)
  }
  setToken(res.access_token)
  return res.access_token
}

export async function register(username: string, password: string, email: string) {
  return request<{ status: string; user_id: number; email_verification: string }>(
    "/create-user",
    { method: "POST", body: { username, password, email }, auth: false },
  )
}

export async function getMe() {
  return request<User>("/me")
}

// ---------- Rooms ----------

export async function getRooms() {
  const res = await request<{ rooms: Room[] }>("/rooms")
  return res.rooms
}

export async function createRoom(room_name: string, invite_code: string) {
  return request<{ status: string; room_id: number; member_id: number }>("/create-room", {
    method: "POST",
    body: { room_name, invite_code },
  })
}

export async function searchRoom(invite_code: string) {
  return request<{ status: string; room_name?: string; reason?: string }>("/search-room", {
    method: "POST",
    body: { invite_code },
  })
}

export async function joinRoom(invite_code: string) {
  return request<{ status: string; room_id?: number; reason?: string }>("/join-room", {
    method: "POST",
    body: { invite_code },
  })
}

export async function getRoomMembers(roomId: number) {
  const res = await request<{ members: RoomMember[] }>(`/rooms/${roomId}/members`)
  return res.members
}

export async function roomWhoami(roomId: number) {
  return request<RoomPermissions>(`/rooms/${roomId}/whoami`)
}

export async function renameRoom(roomId: number, new_name: string) {
  return request(`/rooms/${roomId}`, { method: "PATCH", body: { new_name } })
}

export async function regenerateInviteCode(roomId: number) {
  return request<{ status: string; room_id: number; invite_code: string }>(
    `/rooms/${roomId}/invite-code`,
    { method: "POST" },
  )
}

export async function leaveRoom(roomId: number) {
  return request(`/rooms/${roomId}/members/me`, { method: "DELETE" })
}

export async function deleteRoom(roomId: number) {
  return request(`/rooms/${roomId}`, { method: "DELETE" })
}

export async function removeMember(roomId: number, userId: number) {
  return request(`/rooms/${roomId}/members/${userId}`, { method: "DELETE" })
}

export async function updateMemberRole(roomId: number, userId: number, role: RoomRole) {
  return request(`/rooms/${roomId}/members/${userId}`, {
    method: "PATCH",
    body: { role },
  })
}

// ---------- Events ----------

export async function getUserEvents(from?: string, to?: string) {
  const res = await request<{ events: CalendarEvent[] }>("/events", {
    query: { from, to },
  })
  return res.events
}

export async function getRoomEvents(roomId: number, from?: string, to?: string) {
  const res = await request<{ events: CalendarEvent[] }>(`/rooms/${roomId}/events`, {
    query: { from, to },
  })
  return res.events
}

export async function getEvent(eventId: number) {
  return request<CalendarEvent>(`/events/${eventId}`)
}

export interface CreateEventInput {
  room_id: number
  type: EventType
  title: string
  description: string
  start_time: string
  end_time: string
  visibility: EventVisibility
  status: EventStatus
  min_people: number
}

export async function createEvent(input: CreateEventInput) {
  return request<{ status: string; event_id: number }>("/events", {
    method: "POST",
    body: input,
  })
}

export async function updateEvent(eventId: number, input: Partial<CreateEventInput>) {
  return request(`/events/${eventId}`, { method: "PATCH", body: input })
}

export async function deleteEvent(eventId: number) {
  return request(`/events/${eventId}`, { method: "DELETE" })
}

export async function confirmEvent(eventId: number) {
  return request(`/events/${eventId}/confirm`, { method: "POST" })
}

export async function cancelEvent(eventId: number) {
  return request(`/events/${eventId}/cancel`, { method: "POST" })
}

export async function respondToEvent(eventId: number, status: SignupStatus) {
  return request<{ status: string; going: number }>(`/events/${eventId}/respond`, {
    method: "POST",
    query: { status },
  })
}

export async function getEventResponses(eventId: number) {
  const res = await request<{ responses: EventResponse[] }>(`/events/${eventId}/responses`)
  return res.responses
}
