import { randomUUIDv7 } from 'bun';

import { getDb, pgGet, pgAll } from '@/shared/db';
import { touchDevice } from '@/entities/device';
import { upsertAlbum, listAlbumsByDevice, incrementAlbumPhotoCount } from '@/entities/album';
import { upsertPhoto, listPhotos, getPhoto, markPhotoUploaded } from '@/entities/photo';
import type { Album } from '@/entities/album';
import type { Photo, PhotoFilter } from '@/entities/photo';

import { defaultObjectStore } from './storage';

export interface PhotoPayload {
  localIdentifier: string;
  mediaType: 'image' | 'video' | 'live_photo' | 'audio';
  width: number;
  height: number;
  fileSize?: number | null;
  durationSeconds?: number | null;
  capturedAt: string;
  modifiedAt?: string | null;
  originalFilename?: string | null;
  mimeType?: string | null;
  latitude?: number | null;
  longitude?: number | null;
  locationName?: string | null;
  isFavorite?: boolean | null;
  isHidden?: boolean | null;
  isScreenshot?: boolean | null;
  isSelfie?: boolean | null;
  isBurst?: boolean | null;
  burstIdentifier?: string | null;
  exif?: Record<string, unknown> | null;
  contentHash?: string | null;
}

export interface SyncPhotosPayload {
  photos: PhotoPayload[];
}

export interface AlbumPayload {
  localIdentifier: string;
  title: string;
  albumType: 'user' | 'smart' | 'shared' | 'system';
  photoCount?: number | null;
  photoLocalIdentifiers?: string[] | null;
  coverPhotoLocalIdentifier?: string | null;
  startDate?: string | null;
  endDate?: string | null;
  sortOrder?: number | null;
}

export interface SyncAlbumsPayload {
  albums: AlbumPayload[];
}

export async function ingestPhotos(
  deviceId: string,
  payload: SyncPhotosPayload,
): Promise<{ synced: number; needsUpload: string[]; skipped: number }> {
  const pool = getDb();
  let synced = 0;
  let skipped = 0;
  const needsUpload: string[] = [];

  for (const p of payload.photos) {
    const existing = await pgGet<{ id: string; storage_key: string | null }>(
      pool,
      'SELECT id, storage_key FROM macsync.photos WHERE device_id = $1 AND external_id = $2',
      [deviceId, p.localIdentifier],
    );

    if (existing?.storage_key) {
      skipped++;
      continue;
    }

    const id = existing?.id ?? randomUUIDv7();
    await upsertPhoto(pool, {
      id,
      deviceId,
      externalId: p.localIdentifier,
      filename: p.originalFilename ?? '',
      mediaType: p.mimeType ?? (p.mediaType === 'image' ? 'image/jpeg' : 'video/mp4'),
      width: p.width,
      height: p.height,
      duration: p.durationSeconds ?? null,
      takenAt: p.capturedAt,
    });

    needsUpload.push(p.localIdentifier);
    synced++;
  }

  await touchDevice(pool, deviceId);
  return { synced, needsUpload, skipped };
}

export async function ingestAlbums(deviceId: string, payload: SyncAlbumsPayload): Promise<{ synced: number }> {
  const pool = getDb();
  let synced = 0;

  for (const a of payload.albums) {
    await upsertAlbum(pool, {
      id: randomUUIDv7(),
      deviceId,
      externalId: a.localIdentifier,
      name: a.title,
    });
    synced++;
  }

  return { synced };
}

function storageKeyFor(deviceId: string, photoId: string, ext: string, capturedAt: string): string {
  const d = new Date(capturedAt);
  const y = d.getUTCFullYear();
  const m = String(d.getUTCMonth() + 1).padStart(2, '0');
  return `originals/${deviceId}/${y}/${m}/${photoId}.${ext}`;
}

function extFromFilename(filename: string, mimeType: string): string {
  const fromFile = filename.split('.').pop()?.toLowerCase();
  if (fromFile && ['heic', 'heif', 'jpg', 'jpeg', 'png', 'gif', 'webp', 'mov', 'mp4'].includes(fromFile)) {
    return fromFile;
  }
  const map: Record<string, string> = {
    'image/heic': 'heic', 'image/heif': 'heif', 'image/jpeg': 'jpg',
    'image/png': 'png', 'image/gif': 'gif', 'image/webp': 'webp',
    'video/quicktime': 'mov', 'video/mp4': 'mp4',
  };
  return map[mimeType] ?? 'bin';
}

export async function storePhotoBlob(
  deviceId: string,
  localIdentifier: string,
  contentType: string,
  filename: string,
  stream: ReadableStream,
): Promise<{ photoId: string; storageKey: string }> {
  const pool = getDb();
  const row = await pgGet<{ id: string; taken_at: string | null }>(
    pool,
    'SELECT id, taken_at FROM macsync.photos WHERE device_id = $1 AND external_id = $2',
    [deviceId, localIdentifier],
  );

  if (!row) throw new Error(`photo not found for localIdentifier: ${localIdentifier}`);

  const ext = extFromFilename(filename, contentType);
  const storageKey = storageKeyFor(deviceId, row.id, ext, row.taken_at ?? new Date().toISOString());

  const store = defaultObjectStore();
  await store.put(storageKey, contentType, stream);

  await markPhotoUploaded(pool, row.id, store.bucket, storageKey);
  return { photoId: row.id, storageKey };
}

export async function queryPhotos(filter: PhotoFilter): Promise<readonly Photo[]> {
  return listPhotos(getDb(), filter);
}

export async function queryAlbums(deviceId: string): Promise<readonly Album[]> {
  return listAlbumsByDevice(getDb(), deviceId);
}

export interface IphotoStats {
  totalPhotos: number;
  totalAlbums: number;
  pendingUpload: number;
  lastSyncAt: string | null;
}

export async function getIphotoStats(deviceId: string): Promise<IphotoStats> {
  const pool = getDb();
  const totalRows = await pgAll<{ n: number }>(
    pool,
    'SELECT COUNT(*) as n FROM macsync.photos WHERE device_id = $1',
    [deviceId],
  );
  const pendingRows = await pgAll<{ n: number }>(
    pool,
    'SELECT COUNT(*) as n FROM macsync.photos WHERE device_id = $1 AND storage_key IS NULL',
    [deviceId],
  );
  const albumRows = await pgAll<{ n: number }>(
    pool,
    'SELECT COUNT(*) as n FROM macsync.albums WHERE device_id = $1',
    [deviceId],
  );
  const lastRows = await pgAll<{ last: string | null }>(
    pool,
    'SELECT MAX(synced_at) as last FROM macsync.photos WHERE device_id = $1',
    [deviceId],
  );
  const totalRow = totalRows[0];
  const pendingRow = pendingRows[0];
  const albumRow = albumRows[0];
  const lastRow = lastRows[0];
  return {
    totalPhotos: totalRow?.n ?? 0,
    totalAlbums: albumRow?.n ?? 0,
    pendingUpload: pendingRow?.n ?? 0,
    lastSyncAt: lastRow?.last ?? null,
  };
}

export interface PendingUploadPhoto {
  id: string;
  localIdentifier: string;
  mediaType: string;
  filename: string;
  takenAt: string | null;
  syncedAt: string;
}

export async function listPendingUploads(deviceId: string, limit = 100): Promise<readonly PendingUploadPhoto[]> {
  const pool = getDb();
  const rows = await pgAll<{
    id: string;
    external_id: string;
    media_type: string;
    filename: string;
    taken_at: string | null;
    synced_at: string;
  }>(
    pool,
    'SELECT id, external_id, media_type, filename, taken_at, synced_at FROM macsync.photos WHERE device_id = $1 AND storage_key IS NULL ORDER BY taken_at ASC NULLS LAST LIMIT $2',
    [deviceId, limit],
  );
  return rows.map((r) => ({
    id: r.id,
    localIdentifier: r.external_id,
    mediaType: r.media_type,
    filename: r.filename,
    takenAt: r.taken_at,
    syncedAt: r.synced_at,
  }));
}
