const PHOTOPRISM_API_BASE = '/api/play-apps/photoprism';

export type PhotoPrismStoredSession = {
  baseUrl: string;
  username: string;
  password: string;
  rememberPassword: boolean;
  token: string;
};

export type PhotoPrismAlbum = {
  UID?: string;
  Title?: string;
  Name?: string;
  Caption?: string;
  Thumb?: string;
  Favorite?: boolean;
  Type?: string;
};

export type PhotoPrismFile = {
  UID?: string;
  Name?: string;
  Hash?: string;
  Primary?: boolean;
  Mime?: string;
  Width?: number;
  Height?: number;
};

export type PhotoPrismPhoto = {
  UID: string;
  Title?: string;
  Description?: string;
  TakenAt?: string;
  PlaceLabel?: string;
  Country?: string;
  City?: string;
  CameraModel?: string;
  LensModel?: string;
  LensMake?: string;
  FocalLength?: number;
  FNumber?: number;
  Exposure?: string;
  Iso?: number;
  OriginalName?: string;
  FileName?: string;
  Type?: string;
  Hash?: string;
  Width?: number;
  Height?: number;
  Resolution?: number;
  Filesize?: number;
  Quality?: number;
  Portrait?: boolean;
  Panorama?: boolean;
  LivePhoto?: boolean;
  Favorite?: boolean;
  Albums?: PhotoPrismAlbum[];
  Files?: PhotoPrismFile[];
};

export type PhotoPrismSearchMeta = {
  count: number;
  limit: number;
  offset: number;
  hasMore?: boolean;
  nextOffset?: number;
  previewToken: string;
  downloadToken: string;
};

type PhotoPrismJsonResponse<T> = {
  ok: boolean;
  items: T[];
  meta: PhotoPrismSearchMeta;
};

type PhotoPrismItemResponse<T> = {
  ok: boolean;
  item: T;
};

type PhotoPrismMessageResponse = {
  ok: boolean;
  item?: PhotoPrismPhoto;
  message?: string;
};

async function requestPhotoPrism<T>(path: string, init?: RequestInit): Promise<T> {
  const response = await fetch(`${PHOTOPRISM_API_BASE}${path}`, {
    ...init,
    headers: {
      Accept: 'application/json',
      ...(init?.body ? { 'Content-Type': 'application/json' } : null),
      ...(init?.headers ?? {}),
    },
  });

  let payload: unknown = null;

  try {
    payload = await response.json();
  } catch {
    payload = null;
  }

  if (!response.ok) {
    const message =
      payload && typeof payload === 'object' && 'message' in payload && typeof payload.message === 'string'
        ? payload.message
        : 'PhotoPrism 요청에 실패했습니다.';
    throw new Error(message);
  }

  return payload as T;
}

export async function createPhotoPrismSession(input: {
  baseUrl: string;
  username: string;
  password: string;
}) {
  return requestPhotoPrism<{
    ok: boolean;
    token: string;
    session: Record<string, unknown>;
  }>('/session', {
    method: 'POST',
    body: JSON.stringify(input),
  });
}

export async function fetchPhotoPrismAlbums(input: {
  baseUrl: string;
  token: string;
  q?: string;
  count?: number;
}) {
  const params = new URLSearchParams({
    baseUrl: input.baseUrl,
    token: input.token,
    count: String(input.count ?? 160),
  });

  params.set('q', input.q?.trim() || 'type:album');

  return requestPhotoPrism<PhotoPrismJsonResponse<PhotoPrismAlbum>>(`/albums?${params.toString()}`);
}

export async function fetchPhotoPrismPhotos(input: {
  baseUrl: string;
  token: string;
  albumUid?: string;
  order?: string;
  q?: string;
  count?: number;
  offset?: number;
}) {
  const params = new URLSearchParams({
    baseUrl: input.baseUrl,
    token: input.token,
    count: String(input.count ?? 180),
  });

  if (typeof input.offset === 'number' && Number.isFinite(input.offset) && input.offset > 0) {
    params.set('offset', String(Math.max(0, Math.round(input.offset))));
  }

  if (input.albumUid?.trim()) {
    params.set('albumUid', input.albumUid.trim());
  }

  if (input.order?.trim()) {
    params.set('order', input.order.trim());
  }

  if (input.q?.trim()) {
    params.set('q', input.q.trim());
  }

  return requestPhotoPrism<PhotoPrismJsonResponse<PhotoPrismPhoto>>(`/photos?${params.toString()}`);
}

export async function fetchPhotoPrismPhotoDetail(input: {
  baseUrl: string;
  token: string;
  uid: string;
}) {
  const params = new URLSearchParams({
    baseUrl: input.baseUrl,
    token: input.token,
    uid: input.uid,
  });

  return requestPhotoPrism<PhotoPrismItemResponse<PhotoPrismPhoto>>(`/photo?${params.toString()}`);
}

export async function updatePhotoPrismPhotoFavorite(input: {
  baseUrl: string;
  token: string;
  uid: string;
  favorite: boolean;
}) {
  return requestPhotoPrism<PhotoPrismMessageResponse>('/photo/favorite', {
    method: 'POST',
    body: JSON.stringify(input),
  });
}

export async function updatePhotoPrismFavorite(input: {
  baseUrl: string;
  token: string;
  uid: string;
  favorite: boolean;
}) {
  return requestPhotoPrism<PhotoPrismItemResponse<PhotoPrismPhoto>>('/photo/favorite', {
    method: 'PUT',
    body: JSON.stringify(input),
  });
}

export async function updatePhotoPrismPhotoAlbumMembership(input: {
  baseUrl: string;
  token: string;
  uid: string;
  albumUid: string;
  action: 'add' | 'remove';
}) {
  return requestPhotoPrism<PhotoPrismItemResponse<PhotoPrismPhoto>>('/photo/albums', {
    method: input.action === 'add' ? 'POST' : 'DELETE',
    body: JSON.stringify({
      baseUrl: input.baseUrl,
      token: input.token,
      uid: input.uid,
      albumUid: input.albumUid,
    }),
  });
}

export function buildPhotoPrismThumbUrl(input: {
  baseUrl: string;
  hash: string;
  previewToken: string;
  token?: string;
  size?: string;
}) {
  const params = new URLSearchParams({
    baseUrl: input.baseUrl,
    hash: input.hash,
    previewToken: input.previewToken,
    size: input.size ?? 'fit_720',
  });

  if (input.token?.trim()) {
    params.set('token', input.token.trim());
  }

  return `${PHOTOPRISM_API_BASE}/thumb?${params.toString()}`;
}

export function buildPhotoPrismViewerUrl(input: {
  baseUrl: string;
  token: string;
  uid?: string;
  hash?: string;
  previewToken?: string;
  downloadToken?: string;
  size?: string;
}) {
  const params = new URLSearchParams({
    baseUrl: input.baseUrl,
    token: input.token,
    size: input.size ?? 'fit_2560',
  });

  if (input.uid?.trim()) {
    params.set('uid', input.uid.trim());
  }

  if (input.hash?.trim()) {
    params.set('hash', input.hash.trim());
  }

  if (input.previewToken?.trim()) {
    params.set('previewToken', input.previewToken.trim());
  }

  if (input.downloadToken?.trim()) {
    params.set('downloadToken', input.downloadToken.trim());
  }

  return `${PHOTOPRISM_API_BASE}/viewer?${params.toString()}`;
}

export function buildPhotoPrismDownloadUrl(input: {
  baseUrl: string;
  token: string;
  downloadToken: string;
  uid?: string;
  hash?: string;
}) {
  const params = new URLSearchParams({
    baseUrl: input.baseUrl,
    token: input.token,
    downloadToken: input.downloadToken,
  });

  if (input.uid) {
    params.set('uid', input.uid);
  }

  if (input.hash) {
    params.set('hash', input.hash);
  }

  return `${PHOTOPRISM_API_BASE}/download?${params.toString()}`;
}

export function getPrimaryPhotoFile(photo: PhotoPrismPhoto) {
  return photo.Files?.find((file) => file.Primary) ?? photo.Files?.[0] ?? null;
}

export function isPhotoPrismVideo(photo: PhotoPrismPhoto) {
  const primaryFile = getPrimaryPhotoFile(photo);
  const mime = primaryFile?.Mime?.trim().toLowerCase() ?? '';
  const type = photo.Type?.trim().toLowerCase() ?? '';
  const fileName = (primaryFile?.Name || photo.OriginalName || photo.FileName || '').trim().toLowerCase();

  if (mime.startsWith('video/')) {
    return true;
  }

  if (type.includes('video')) {
    return true;
  }

  return /\.(mp4|mov|m4v|webm|avi|mkv|3gp|mts|m2ts)$/i.test(fileName);
}

export function getPhotoDisplayTitle(photo: PhotoPrismPhoto) {
  return photo.Title?.trim() || photo.OriginalName?.trim() || photo.FileName?.trim() || photo.UID;
}

export function getPhotoDisplayDate(photo: PhotoPrismPhoto) {
  if (!photo.TakenAt) {
    return '';
  }

  try {
    return new Intl.DateTimeFormat('ko-KR', {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
    }).format(new Date(photo.TakenAt));
  } catch {
    return photo.TakenAt;
  }
}
