import { randomUUIDv7 } from 'bun';

import { getDb } from '@/shared/db';
import { touchDevice } from '@/entities/device';
import { upsertCall, listCalls, countCalls, getLastCallSync } from '@/entities/call';
import type { CallRecord, CallDraft, CallFilter } from '@/entities/call';

export interface CallPayload {
  uniqueId: string;
  address?: string | null;
  normalizedAddress?: string | null;
  contactName?: string | null;
  direction?: 'incoming' | 'outgoing' | string | null;
  callType?: string | null;
  answered?: boolean | null;
  durationSeconds?: number | null;
  startedAt?: string | null;
  serviceProvider?: string | null;
}

export interface SyncCallsPayload {
  calls: CallPayload[];
}

export async function ingestCalls(
  deviceId: string,
  payload: SyncCallsPayload,
): Promise<{ synced: number; skipped: number; calls_synced?: number }> {
  try {
    const db = getDb();
    let synced = 0;

    // Upsert (not insert-if-absent): the `(device_id, external_id)` conflict
    // target refreshes mutable fields in place, so a `forceFullResync` can heal
    // rows written by older reader bugs (e.g. the Int64 direction/answered
    // miscast). The generated `id` is only used on first insert; on conflict the
    // existing row id is preserved.
    for (const c of payload.calls) {
      const draft: CallDraft = {
        id: randomUUIDv7(),
        deviceId,
        externalId: c.uniqueId,
        address: c.address ?? null,
        normalizedAddress: c.normalizedAddress ?? null,
        contactName: c.contactName ?? null,
        direction: c.direction ?? null,
        callType: c.callType ?? null,
        answered: c.answered ?? null,
        durationSeconds: c.durationSeconds ?? null,
        startedAt: c.startedAt ?? null,
        serviceProvider: c.serviceProvider ?? null,
      };
      await upsertCall(db, draft);
      synced++;
    }

    await touchDevice(db, deviceId);
    // `synced` is what the Swift client reads back (APIClient reads data.synced);
    // `calls_synced` is what sync-history records. Keep them in lock-step.
    return { synced, skipped: 0, calls_synced: synced };
  } catch (err) {
    throw new Error(
      `ingestCalls failed for device ${deviceId}: ${err instanceof Error ? err.message : String(err)}`,
      { cause: err },
    );
  }
}

export async function queryCalls(filter: CallFilter = {}): Promise<readonly CallRecord[]> {
  try {
    return await listCalls(getDb(), filter);
  } catch (err) {
    throw new Error(
      `queryCalls failed: ${err instanceof Error ? err.message : String(err)}`,
      { cause: err },
    );
  }
}

export async function getCallStats(
  deviceId?: string,
): Promise<{ totalCalls: number; lastSyncAt: string | null }> {
  try {
    const db = getDb();
    const total = await countCalls(db, deviceId);
    const lastSyncAt = deviceId ? await getLastCallSync(db, deviceId) : null;
    return { totalCalls: total, lastSyncAt };
  } catch (err) {
    throw new Error(
      `getCallStats failed: ${err instanceof Error ? err.message : String(err)}`,
      { cause: err },
    );
  }
}
