import { getAuthToken } from "@/utils/token";
import { NONE_SELECT_VALUE, type EventRecord, type ImageRecord, type SelectOption } from "./types";

interface BrapiPagination {
  currentPage: number;
  pageSize: number;
  totalCount: number;
  totalPages: number;
}

interface BrapiListResponse<T> {
  metadata: {
    pagination: BrapiPagination;
    status: Array<Record<string, unknown>>;
    datafiles: Array<Record<string, unknown>>;
  };
  result: {
    data: T[];
  };
}

interface BrapiSingleResponse<T> {
  metadata: {
    pagination: BrapiPagination;
    status: Array<Record<string, unknown>>;
    datafiles: Array<Record<string, unknown>>;
  };
  result: T;
}

interface StudyResponse {
  studyDbId: string;
  studyName: string | null;
}

interface ObservationUnitResponse {
  observationUnitDbId: string;
  observationUnitName: string | null;
}

interface ObservationResponse {
  observationDbId: string;
  observationUnitName: string | null;
  observationVariableName: string | null;
}

type EventPayload = Partial<Record<
  | "id"
  | "event_type"
  | "event_type_db_id"
  | "event_description"
  | "study_db_id"
  | "observation_unit_db_ids"
  | "start_date"
  | "end_date",
  unknown
>>;

type ImagePayload = Partial<Record<
  | "id"
  | "image_name"
  | "image_file_name"
  | "image_url"
  | "mime_type"
  | "description"
  | "study_db_id"
  | "observation_unit_db_id"
  | "observation_db_ids"
  | "image_time_stamp",
  unknown
>>;

const apiBase = () => {
  if (typeof window !== "undefined") return "";
  return process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000";
};

async function request<T>(path: string, init?: RequestInit): Promise<T> {
  const token = getAuthToken();
  const response = await fetch(`${apiBase()}${path}`, {
    ...init,
    headers: {
      "Content-Type": "application/json",
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
      ...(init?.headers || {}),
    },
  });

  if (!response.ok) {
    const detail = await response.text();
    throw new Error(detail || `Request failed: ${response.status}`);
  }
  return response.json() as Promise<T>;
}

const optionalText = (value: unknown) => {
  const normalized = String(value ?? "").trim();
  if (!normalized || normalized === NONE_SELECT_VALUE) return null;
  return normalized;
};

const requiredText = (value: unknown, message: string) => {
  const normalized = optionalText(value);
  if (!normalized) throw new Error(message);
  return normalized;
};

const csvToArray = (value: unknown) => {
  const normalized = optionalText(value);
  if (!normalized) return [];
  return normalized
    .split(",")
    .map((part) => part.trim())
    .filter(Boolean);
};

const arrayToCsv = (value: unknown) => {
  if (!Array.isArray(value)) return null;
  return value.map((item) => String(item)).filter(Boolean).join(", ");
};

const mapEvent = (event: EventRecord): EventRecord => {
  const startDate = event.start_date || event.eventDateRange?.startDate || null;
  const endDate = event.end_date || event.eventDateRange?.endDate || null;
  const unitIds = Array.isArray(event.observationUnitDbIds) ? event.observationUnitDbIds : [];
  return {
    ...event,
    id: event.eventDbId || event.id,
    event_type: event.event_type || event.eventType || null,
    event_type_db_id: event.event_type_db_id || event.eventTypeDbId || null,
    event_description: event.event_description || event.eventDescription || null,
    study_db_id: event.study_db_id || event.studyDbId || null,
    observation_unit_db_ids: event.observation_unit_db_ids || arrayToCsv(unitIds),
    start_date: startDate,
    end_date: endDate,
  };
};

const mapImage = (image: ImageRecord): ImageRecord => {
  const observationIds = Array.isArray(image.observationDbIds) ? image.observationDbIds : [];
  return {
    ...image,
    id: image.imageDbId || image.id,
    image_name: image.image_name || image.imageName || null,
    image_file_name: image.image_file_name || image.imageFileName || null,
    image_url: image.image_url || image.imageURL || null,
    mime_type: image.mime_type || image.mimeType || null,
    study_db_id: image.study_db_id || image.studyDbId || null,
    observation_unit_db_id: image.observation_unit_db_id || image.observationUnitDbId || null,
    observation_db_ids: image.observation_db_ids || arrayToCsv(observationIds),
    image_time_stamp: image.image_time_stamp || image.imageTimeStamp || null,
    image_file_size: image.image_file_size ?? image.imageFileSize ?? null,
    image_width: image.image_width ?? image.imageWidth ?? null,
    image_height: image.image_height ?? image.imageHeight ?? null,
  };
};

const eventBody = (payload: EventPayload) => ({
  eventType: requiredText(payload.event_type, "Event type is required"),
  eventTypeDbId: optionalText(payload.event_type_db_id),
  eventDescription: optionalText(payload.event_description),
  studyDbId: optionalText(payload.study_db_id),
  observationUnitDbIds: csvToArray(payload.observation_unit_db_ids),
  eventDateRange: {
    startDate: optionalText(payload.start_date),
    endDate: optionalText(payload.end_date),
  },
});

const imageBody = (payload: ImagePayload) => ({
  imageName: requiredText(payload.image_name, "Image name is required"),
  imageFileName: optionalText(payload.image_file_name),
  imageURL: optionalText(payload.image_url),
  mimeType: optionalText(payload.mime_type),
  description: optionalText(payload.description),
  studyDbId: optionalText(payload.study_db_id),
  observationUnitDbId: optionalText(payload.observation_unit_db_id),
  observationDbIds: csvToArray(payload.observation_db_ids),
  imageTimeStamp: optionalText(payload.image_time_stamp),
  imageFileSize: null,
  imageWidth: null,
  imageHeight: null,
});

export async function fetchEventRows(): Promise<EventRecord[]> {
  const response = await request<BrapiListResponse<EventRecord>>("/brapi/v2/events?page=0&pageSize=10");
  return response.result.data.map(mapEvent);
}

export async function fetchImageRows(): Promise<ImageRecord[]> {
  const response = await request<BrapiListResponse<ImageRecord>>("/brapi/v2/images?page=0&pageSize=10");
  return response.result.data.map(mapImage);
}

export async function fetchEventImageOptions(): Promise<{
  studies: SelectOption[];
  observationUnits: SelectOption[];
  observations: SelectOption[];
}> {
  const [studies, observationUnits, observations] = await Promise.all([
    request<BrapiListResponse<StudyResponse>>("/brapi/v2/studies?page=0&pageSize=10"),
    request<BrapiListResponse<ObservationUnitResponse>>("/brapi/v2/observationunits?page=0&pageSize=10"),
    request<BrapiListResponse<ObservationResponse>>("/brapi/v2/observations?page=0&pageSize=10"),
  ]);

  return {
    studies: studies.result.data.map((study) => ({
      value: study.studyDbId,
      label: study.studyName || study.studyDbId,
    })),
    observationUnits: observationUnits.result.data.map((unit) => ({
      value: unit.observationUnitDbId,
      label: unit.observationUnitName || unit.observationUnitDbId,
    })),
    observations: observations.result.data.map((observation) => ({
      value: observation.observationDbId,
      label: `${observation.observationDbId}${observation.observationUnitName ? ` / ${observation.observationUnitName}` : ""}${observation.observationVariableName ? ` / ${observation.observationVariableName}` : ""}`,
    })),
  };
}

export async function createEventRow(payload: EventPayload): Promise<EventRecord> {
  const response = await request<BrapiListResponse<EventRecord>>("/brapi/v2/events", {
    method: "POST",
    body: JSON.stringify({
      eventDbId: requiredText(payload.id, "Event ID is required"),
      ...eventBody(payload),
    }),
  });
  return mapEvent(response.result.data[0]);
}

export async function updateEventRow(id: string, payload: EventPayload): Promise<EventRecord> {
  const requestedId = optionalText(payload.id);
  if (requestedId && requestedId !== id) {
    throw new Error("Event ID is immutable. Create a new record instead.");
  }
  const response = await request<BrapiListResponse<EventRecord>>(`/brapi/v2/events/${encodeURIComponent(id)}`, {
    method: "PUT",
    body: JSON.stringify(eventBody(payload)),
  });
  return mapEvent(response.result.data[0]);
}

export async function deleteEventRow(id: string): Promise<void> {
  await request<BrapiSingleResponse<EventRecord>>(`/brapi/v2/events/${encodeURIComponent(id)}`, {
    method: "DELETE",
  });
}

export async function createImageRow(payload: ImagePayload): Promise<ImageRecord> {
  const response = await request<BrapiListResponse<ImageRecord>>("/brapi/v2/images", {
    method: "POST",
    body: JSON.stringify({
      imageDbId: requiredText(payload.id, "Image ID is required"),
      ...imageBody(payload),
    }),
  });
  return mapImage(response.result.data[0]);
}

export async function updateImageRow(id: string, payload: ImagePayload): Promise<ImageRecord> {
  const requestedId = optionalText(payload.id);
  if (requestedId && requestedId !== id) {
    throw new Error("Image ID is immutable. Create a new record instead.");
  }
  const response = await request<BrapiListResponse<ImageRecord>>(`/brapi/v2/images/${encodeURIComponent(id)}`, {
    method: "PUT",
    body: JSON.stringify(imageBody(payload)),
  });
  return mapImage(response.result.data[0]);
}

export async function deleteImageRow(id: string): Promise<void> {
  await request<BrapiSingleResponse<ImageRecord>>(`/brapi/v2/images/${encodeURIComponent(id)}`, {
    method: "DELETE",
  });
}
