export type SystemNotificationData = {
  notificationLabel?: string;
  senderName?: string;

  meetingRequestTitle?: string;
  meetingRequestId?: number;
  meetingId?: number;
  meetingTitle?: string;
  meetingDate?: string | null;
  startTime?: string | null;
  endTime?: string | null;
  location?: string | null;

  rgcDecisionId?: number;
  plenaryId?: number;
  plenaryName?: string;
  stakeholderId?: number;
  ministryName?: string;
  categoryId?: number;
  categoryName?: string;
  status?: string;

  url?: string;
};

export type SystemNotification = {
  id: number;
  title: string;
  message: string;
  type: string;
  isRead: boolean;
  readAt: string | null;
  senderUserId: number | null;
  receiverUserId: number | null;
  receiverStakeholderId: number | null;
  meetingRequestId: number | null;
  data: SystemNotificationData | null;
  createdAt: string;
  updatedAt: string;
  deletedAt: string | null;
};

export type SystemNotificationListData = {
  data: SystemNotification[];
  meta: {
    page: number;
    limit: number;
    total: number;
    unreadCount: number;
    totalPages: number;
  };
};

type ApiResponse<T> = {
  success?: boolean;
  statusCode?: number;
  message?: string | string[];
  data?: T;
  meta?: SystemNotificationListData["meta"];
};

const EMPTY_LIST_META: SystemNotificationListData["meta"] = {
  page: 1,
  limit: 0,
  total: 0,
  unreadCount: 0,
  totalPages: 0,
};

const API_BASE_URL = (
  process.env.NEXT_PUBLIC_API_URL?.trim() ||
  "http://localhost:3001/api/v1"
).replace(/\/+$/, "");

/*
 * The backend decides which notifications belong to us.
 * It reads the login cookie, then returns the notifications sent to our user
 * plus the ones sent to any stakeholder we belong to. That is why no request
 * below sends a receiver ID: the server would ignore it anyway.
 */
function getHeaders(): HeadersInit {
  return {
    Accept: "application/json",
    "Content-Type": "application/json",
  };
}

function getApiUrl(path: string): string {
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;

  return `${API_BASE_URL}${normalizedPath}`;
}

function getErrorMessage(message: string | string[] | undefined) {
  if (Array.isArray(message)) {
    return message.join(", ");
  }

  if (typeof message === "string" && message.trim()) {
    return message;
  }

  return "";
}

async function parseResponse<T>(response: Response): Promise<T> {
  let payload: ApiResponse<T> | T;

  try {
    payload = (await response.json()) as ApiResponse<T> | T;
  } catch {
    throw new Error(
      `Notification API returned invalid JSON. Status: ${response.status}`,
    );
  }

  const apiResponse = payload as ApiResponse<T>;

  if (!response.ok || apiResponse.success === false) {
    throw new Error(
      getErrorMessage(apiResponse.message) ||
        `Notification API request failed. Status: ${response.status}`,
    );
  }

  const isWrappedResponse =
    typeof payload === "object" &&
    payload !== null &&
    "data" in payload;

  if (isWrappedResponse && apiResponse.data !== undefined) {
    return apiResponse.data;
  }

  return payload as T;
}

async function parseListResponse(
  response: Response,
): Promise<SystemNotificationListData> {
  let payload: ApiResponse<SystemNotification[]>;

  try {
    payload = (await response.json()) as ApiResponse<SystemNotification[]>;
  } catch {
    throw new Error(
      `Notification API returned invalid JSON. Status: ${response.status}`,
    );
  }

  if (!response.ok || payload.success === false) {
    throw new Error(
      getErrorMessage(payload.message) ||
        `Notification API request failed. Status: ${response.status}`,
    );
  }

  return {
    data: Array.isArray(payload.data) ? payload.data : [],
    meta: payload.meta ?? EMPTY_LIST_META,
  };
}

/*
 * Tells every notification view on the page to reload itself.
 * Use this after anything that changes the unread count.
 */
export function dispatchSystemNotificationUpdated() {
  if (typeof window === "undefined") {
    return;
  }

  window.dispatchEvent(new Event("system-notification-updated"));
}

export async function getSystemNotifications(params: {
  page?: number;
  limit?: number;
  isRead?: boolean;
  type?: string;
}): Promise<SystemNotificationListData> {
  const searchParams = new URLSearchParams({
    page: String(params.page ?? 1),
    limit: String(params.limit ?? 20),
  });

  if (typeof params.isRead === "boolean") {
    searchParams.set("isRead", String(params.isRead));
  }

  if (params.type?.trim()) {
    searchParams.set("type", params.type.trim());
  }

  const response = await fetch(
    getApiUrl(`/system-notifications?${searchParams.toString()}`),
    {
      method: "GET",
      headers: getHeaders(),
      credentials: "include",
      cache: "no-store",
    },
  );

  return parseListResponse(response);
}

export async function markSystemNotificationAsRead(
  notificationId: number,
): Promise<SystemNotification> {
  const response = await fetch(
    getApiUrl(`/system-notifications/${notificationId}/read`),
    {
      method: "PATCH",
      headers: getHeaders(),
      credentials: "include",
    },
  );

  const result = await parseResponse<SystemNotification>(response);

  dispatchSystemNotificationUpdated();

  return result;
}

export async function markAllSystemNotificationsAsRead(): Promise<{
  updated: number;
}> {
  const response = await fetch(getApiUrl("/system-notifications/read-all"), {
    method: "PATCH",
    headers: getHeaders(),
    credentials: "include",
    body: JSON.stringify({}),
  });

  const result = await parseResponse<{ updated: number }>(response);

  dispatchSystemNotificationUpdated();

  return result;
}
