import { db, canAccessTrip } from '../db/database';
import { maybe_encrypt_api_key, decrypt_api_key } from './apiKeyCrypto';
import { checkSsrf } from '../utils/ssrfGuard';
import { writeAudit } from './auditLog';

// ── Credentials ────────────────────────────────────────────────────────────

export function getImmichCredentials(userId: number) {
  const user = db.prepare('SELECT immich_url, immich_api_key FROM users WHERE id = ?').get(userId) as any;
  if (!user?.immich_url || !user?.immich_api_key) return null;
  return { immich_url: user.immich_url as string, immich_api_key: decrypt_api_key(user.immich_api_key) as string };
}

/** Validate that an asset ID is a safe UUID-like string (no path traversal). */
export function isValidAssetId(id: string): boolean {
  return /^[a-zA-Z0-9_-]+$/.test(id) && id.length <= 100;
}

// ── Connection Settings ────────────────────────────────────────────────────

export function getConnectionSettings(userId: number) {
  const creds = getImmichCredentials(userId);
  return {
    immich_url: creds?.immich_url || '',
    connected: !!(creds?.immich_url && creds?.immich_api_key),
  };
}

export async function saveImmichSettings(
  userId: number,
  immichUrl: string | undefined,
  immichApiKey: string | undefined,
  clientIp: string | null
): Promise<{ success: boolean; warning?: string; error?: string }> {
  if (immichUrl) {
    const ssrf = await checkSsrf(immichUrl.trim());
    if (!ssrf.allowed) {
      return { success: false, error: `Invalid Immich URL: ${ssrf.error}` };
    }
    db.prepare('UPDATE users SET immich_url = ?, immich_api_key = ? WHERE id = ?').run(
      immichUrl.trim(),
      maybe_encrypt_api_key(immichApiKey),
      userId
    );
    if (ssrf.isPrivate) {
      writeAudit({
        userId,
        action: 'immich.private_ip_configured',
        ip: clientIp,
        details: { immich_url: immichUrl.trim(), resolved_ip: ssrf.resolvedIp },
      });
      return {
        success: true,
        warning: `Immich URL resolves to a private IP address (${ssrf.resolvedIp}). Make sure this is intentional.`,
      };
    }
  } else {
    db.prepare('UPDATE users SET immich_url = ?, immich_api_key = ? WHERE id = ?').run(
      null,
      maybe_encrypt_api_key(immichApiKey),
      userId
    );
  }
  return { success: true };
}

// ── Connection Test / Status ───────────────────────────────────────────────

export async function testConnection(
  immichUrl: string,
  immichApiKey: string
): Promise<{ connected: boolean; error?: string; user?: { name?: string; email?: string }; canonicalUrl?: string }> {
  const ssrf = await checkSsrf(immichUrl);
  if (!ssrf.allowed) return { connected: false, error: ssrf.error ?? 'Invalid Immich URL' };
  try {
    const resp = await fetch(`${immichUrl}/api/users/me`, {
      headers: { 'x-api-key': immichApiKey, 'Accept': 'application/json' },
      signal: AbortSignal.timeout(10000),
    });
    if (!resp.ok) return { connected: false, error: `HTTP ${resp.status}` };
    const data = await resp.json() as { name?: string; email?: string };

    // Detect http → https upgrade only: same host/port, protocol changed to https
    let canonicalUrl: string | undefined;
    if (resp.url) {
      const finalUrl = new URL(resp.url);
      const inputUrl = new URL(immichUrl);
      if (
        inputUrl.protocol === 'http:' &&
        finalUrl.protocol === 'https:' &&
        finalUrl.hostname === inputUrl.hostname &&
        finalUrl.port === inputUrl.port
      ) {
        canonicalUrl = finalUrl.origin;
      }
    }

    return { connected: true, user: { name: data.name, email: data.email }, canonicalUrl };
  } catch (err: unknown) {
    return { connected: false, error: err instanceof Error ? err.message : 'Connection failed' };
  }
}

export async function getConnectionStatus(
  userId: number
): Promise<{ connected: boolean; error?: string; user?: { name?: string; email?: string } }> {
  const creds = getImmichCredentials(userId);
  if (!creds) return { connected: false, error: 'Not configured' };
  try {
    const resp = await fetch(`${creds.immich_url}/api/users/me`, {
      headers: { 'x-api-key': creds.immich_api_key, 'Accept': 'application/json' },
      signal: AbortSignal.timeout(10000),
    });
    if (!resp.ok) return { connected: false, error: `HTTP ${resp.status}` };
    const data = await resp.json() as { name?: string; email?: string };
    return { connected: true, user: { name: data.name, email: data.email } };
  } catch (err: unknown) {
    return { connected: false, error: err instanceof Error ? err.message : 'Connection failed' };
  }
}

// ── Browse Timeline / Search ───────────────────────────────────────────────

export async function browseTimeline(
  userId: number
): Promise<{ buckets?: any; error?: string; status?: number }> {
  const creds = getImmichCredentials(userId);
  if (!creds) return { error: 'Immich not configured', status: 400 };

  try {
    const resp = await fetch(`${creds.immich_url}/api/timeline/buckets`, {
      method: 'GET',
      headers: { 'x-api-key': creds.immich_api_key, 'Accept': 'application/json' },
      signal: AbortSignal.timeout(15000),
    });
    if (!resp.ok) return { error: 'Failed to fetch from Immich', status: resp.status };
    const buckets = await resp.json();
    return { buckets };
  } catch {
    return { error: 'Could not reach Immich', status: 502 };
  }
}

export async function searchPhotos(
  userId: number,
  from?: string,
  to?: string
): Promise<{ assets?: any[]; error?: string; status?: number }> {
  const creds = getImmichCredentials(userId);
  if (!creds) return { error: 'Immich not configured', status: 400 };

  try {
    // Paginate through all results (Immich limits per-page to 1000)
    const allAssets: any[] = [];
    let page = 1;
    const pageSize = 1000;
    while (true) {
      const resp = await fetch(`${creds.immich_url}/api/search/metadata`, {
        method: 'POST',
        headers: { 'x-api-key': creds.immich_api_key, 'Content-Type': 'application/json' },
        body: JSON.stringify({
          takenAfter: from ? `${from}T00:00:00.000Z` : undefined,
          takenBefore: to ? `${to}T23:59:59.999Z` : undefined,
          type: 'IMAGE',
          size: pageSize,
          page,
        }),
        signal: AbortSignal.timeout(15000),
      });
      if (!resp.ok) return { error: 'Search failed', status: resp.status };
      const data = await resp.json() as { assets?: { items?: any[] } };
      const items = data.assets?.items || [];
      allAssets.push(...items);
      if (items.length < pageSize) break; // Last page
      page++;
      if (page > 20) break; // Safety limit (20k photos max)
    }
    const assets = allAssets.map((a: any) => ({
      id: a.id,
      takenAt: a.fileCreatedAt || a.createdAt,
      city: a.exifInfo?.city || null,
      country: a.exifInfo?.country || null,
    }));
    return { assets };
  } catch {
    return { error: 'Could not reach Immich', status: 502 };
  }
}

// ── Trip Photos ────────────────────────────────────────────────────────────

export function listTripPhotos(tripId: string, userId: number) {
  return db.prepare(`
    SELECT tp.immich_asset_id, tp.user_id, tp.shared, tp.added_at,
           u.username, u.avatar, u.immich_url
    FROM trip_photos tp
    JOIN users u ON tp.user_id = u.id
    WHERE tp.trip_id = ?
    AND (tp.user_id = ? OR tp.shared = 1)
    ORDER BY tp.added_at ASC
  `).all(tripId, userId);
}

export function addTripPhotos(
  tripId: string,
  userId: number,
  assetIds: string[],
  shared: boolean
): number {
  const insert = db.prepare(
    'INSERT OR IGNORE INTO trip_photos (trip_id, user_id, immich_asset_id, shared) VALUES (?, ?, ?, ?)'
  );
  let added = 0;
  for (const assetId of assetIds) {
    const result = insert.run(tripId, userId, assetId, shared ? 1 : 0);
    if (result.changes > 0) added++;
  }
  return added;
}

export function removeTripPhoto(tripId: string, userId: number, assetId: string) {
  db.prepare('DELETE FROM trip_photos WHERE trip_id = ? AND user_id = ? AND immich_asset_id = ?')
    .run(tripId, userId, assetId);
}

export function togglePhotoSharing(tripId: string, userId: number, assetId: string, shared: boolean) {
  db.prepare('UPDATE trip_photos SET shared = ? WHERE trip_id = ? AND user_id = ? AND immich_asset_id = ?')
    .run(shared ? 1 : 0, tripId, userId, assetId);
}

// ── Asset Info / Proxy ─────────────────────────────────────────────────────

/**
 * Verify that requestingUserId can access a shared photo belonging to ownerUserId.
 * The asset must be shared (shared=1) and the requesting user must be a member of
 * the same trip that contains the photo.
 */
export function canAccessUserPhoto(requestingUserId: number, ownerUserId: number, assetId: string): boolean {
  const rows = db.prepare(`
    SELECT tp.trip_id FROM trip_photos tp
    WHERE tp.immich_asset_id = ? AND tp.user_id = ? AND tp.shared = 1
  `).all(assetId, ownerUserId) as { trip_id: number }[];
  if (rows.length === 0) return false;
  return rows.some(row => !!canAccessTrip(String(row.trip_id), requestingUserId));
}

export async function getAssetInfo(
  userId: number,
  assetId: string,
  ownerUserId?: number
): Promise<{ data?: any; error?: string; status?: number }> {
  const effectiveUserId = ownerUserId ?? userId;
  const creds = getImmichCredentials(effectiveUserId);
  if (!creds) return { error: 'Not found', status: 404 };

  try {
    const resp = await fetch(`${creds.immich_url}/api/assets/${assetId}`, {
      headers: { 'x-api-key': creds.immich_api_key, 'Accept': 'application/json' },
      signal: AbortSignal.timeout(10000),
    });
    if (!resp.ok) return { error: 'Failed', status: resp.status };
    const asset = await resp.json() as any;
    return {
      data: {
        id: asset.id,
        takenAt: asset.fileCreatedAt || asset.createdAt,
        width: asset.exifInfo?.exifImageWidth || null,
        height: asset.exifInfo?.exifImageHeight || null,
        camera: asset.exifInfo?.make && asset.exifInfo?.model ? `${asset.exifInfo.make} ${asset.exifInfo.model}` : null,
        lens: asset.exifInfo?.lensModel || null,
        focalLength: asset.exifInfo?.focalLength ? `${asset.exifInfo.focalLength}mm` : null,
        aperture: asset.exifInfo?.fNumber ? `f/${asset.exifInfo.fNumber}` : null,
        shutter: asset.exifInfo?.exposureTime || null,
        iso: asset.exifInfo?.iso || null,
        city: asset.exifInfo?.city || null,
        state: asset.exifInfo?.state || null,
        country: asset.exifInfo?.country || null,
        lat: asset.exifInfo?.latitude || null,
        lng: asset.exifInfo?.longitude || null,
        fileSize: asset.exifInfo?.fileSizeInByte || null,
        fileName: asset.originalFileName || null,
      },
    };
  } catch {
    return { error: 'Proxy error', status: 502 };
  }
}

export async function proxyThumbnail(
  userId: number,
  assetId: string,
  ownerUserId?: number
): Promise<{ buffer?: Buffer; contentType?: string; error?: string; status?: number }> {
  const effectiveUserId = ownerUserId ?? userId;
  const creds = getImmichCredentials(effectiveUserId);
  if (!creds) return { error: 'Not found', status: 404 };

  try {
    const resp = await fetch(`${creds.immich_url}/api/assets/${assetId}/thumbnail`, {
      headers: { 'x-api-key': creds.immich_api_key },
      signal: AbortSignal.timeout(10000),
    });
    if (!resp.ok) return { error: 'Failed', status: resp.status };
    const buffer = Buffer.from(await resp.arrayBuffer());
    const contentType = resp.headers.get('content-type') || 'image/webp';
    return { buffer, contentType };
  } catch {
    return { error: 'Proxy error', status: 502 };
  }
}

export async function proxyOriginal(
  userId: number,
  assetId: string,
  ownerUserId?: number
): Promise<{ buffer?: Buffer; contentType?: string; error?: string; status?: number }> {
  const effectiveUserId = ownerUserId ?? userId;
  const creds = getImmichCredentials(effectiveUserId);
  if (!creds) return { error: 'Not found', status: 404 };

  try {
    const resp = await fetch(`${creds.immich_url}/api/assets/${assetId}/original`, {
      headers: { 'x-api-key': creds.immich_api_key },
      signal: AbortSignal.timeout(30000),
    });
    if (!resp.ok) return { error: 'Failed', status: resp.status };
    const buffer = Buffer.from(await resp.arrayBuffer());
    const contentType = resp.headers.get('content-type') || 'image/jpeg';
    return { buffer, contentType };
  } catch {
    return { error: 'Proxy error', status: 502 };
  }
}

// ── Albums ──────────────────────────────────────────────────────────────────

export async function listAlbums(
  userId: number
): Promise<{ albums?: any[]; error?: string; status?: number }> {
  const creds = getImmichCredentials(userId);
  if (!creds) return { error: 'Immich not configured', status: 400 };

  try {
    const resp = await fetch(`${creds.immich_url}/api/albums`, {
      headers: { 'x-api-key': creds.immich_api_key, 'Accept': 'application/json' },
      signal: AbortSignal.timeout(10000),
    });
    if (!resp.ok) return { error: 'Failed to fetch albums', status: resp.status };
    const albums = (await resp.json() as any[]).map((a: any) => ({
      id: a.id,
      albumName: a.albumName,
      assetCount: a.assetCount || 0,
      startDate: a.startDate,
      endDate: a.endDate,
      albumThumbnailAssetId: a.albumThumbnailAssetId,
    }));
    return { albums };
  } catch {
    return { error: 'Could not reach Immich', status: 502 };
  }
}

export function listAlbumLinks(tripId: string) {
  return db.prepare(`
    SELECT tal.*, u.username
    FROM trip_album_links tal
    JOIN users u ON tal.user_id = u.id
    WHERE tal.trip_id = ?
    ORDER BY tal.created_at ASC
  `).all(tripId);
}

export function createAlbumLink(
  tripId: string,
  userId: number,
  albumId: string,
  albumName: string
): { success: boolean; error?: string } {
  try {
    db.prepare(
      'INSERT OR IGNORE INTO trip_album_links (trip_id, user_id, immich_album_id, album_name) VALUES (?, ?, ?, ?)'
    ).run(tripId, userId, albumId, albumName || '');
    return { success: true };
  } catch {
    return { success: false, error: 'Album already linked' };
  }
}

export function deleteAlbumLink(linkId: string, tripId: string, userId: number) {
  db.prepare('DELETE FROM trip_album_links WHERE id = ? AND trip_id = ? AND user_id = ?')
    .run(linkId, tripId, userId);
}

export async function syncAlbumAssets(
  tripId: string,
  linkId: string,
  userId: number
): Promise<{ success?: boolean; added?: number; total?: number; error?: string; status?: number }> {
  const link = db.prepare('SELECT * FROM trip_album_links WHERE id = ? AND trip_id = ? AND user_id = ?')
    .get(linkId, tripId, userId) as any;
  if (!link) return { error: 'Album link not found', status: 404 };

  const creds = getImmichCredentials(userId);
  if (!creds) return { error: 'Immich not configured', status: 400 };

  try {
    const resp = await fetch(`${creds.immich_url}/api/albums/${link.immich_album_id}`, {
      headers: { 'x-api-key': creds.immich_api_key, 'Accept': 'application/json' },
      signal: AbortSignal.timeout(15000),
    });
    if (!resp.ok) return { error: 'Failed to fetch album', status: resp.status };
    const albumData = await resp.json() as { assets?: any[] };
    const assets = (albumData.assets || []).filter((a: any) => a.type === 'IMAGE');

    const insert = db.prepare(
      'INSERT OR IGNORE INTO trip_photos (trip_id, user_id, immich_asset_id, shared) VALUES (?, ?, ?, 1)'
    );
    let added = 0;
    for (const asset of assets) {
      const r = insert.run(tripId, userId, asset.id);
      if (r.changes > 0) added++;
    }

    db.prepare('UPDATE trip_album_links SET last_synced_at = CURRENT_TIMESTAMP WHERE id = ?').run(linkId);

    return { success: true, added, total: assets.length };
  } catch {
    return { error: 'Could not reach Immich', status: 502 };
  }
}
