import type { Pool } from 'pg';

import { pgAll, pgGet, pgRun } from '@/shared/db';

import type { CallDraft, CallFilter, CallRecord } from './types';

interface Row {
  id: string;
  device_id: string;
  external_id: string;
  address: string | null;
  normalized_address: string | null;
  contact_name: string | null;
  direction: string | null;
  call_type: string | null;
  answered: boolean | null;
  duration_seconds: number | null;
  started_at: string | null;
  service_provider: string | null;
  synced_at: string;
}

const hydrate = (r: Row): CallRecord => ({
  id: r.id,
  deviceId: r.device_id,
  externalId: r.external_id,
  address: r.address,
  normalizedAddress: r.normalized_address,
  contactName: r.contact_name,
  direction: r.direction,
  callType: r.call_type,
  answered: r.answered,
  durationSeconds: r.duration_seconds,
  startedAt: r.started_at,
  serviceProvider: r.service_provider,
  syncedAt: r.synced_at,
});

function wrap(err: unknown, op: string): Error {
  return new Error(`call/repo.${op} failed: ${err instanceof Error ? err.message : String(err)}`, {
    cause: err,
  });
}

export async function upsertCall(pool: Pool, draft: CallDraft): Promise<void> {
  try {
    await pgRun(
      pool,
      `INSERT INTO macsync.calls
         (id, device_id, external_id, address, normalized_address, contact_name,
          direction, call_type, answered, duration_seconds, started_at, service_provider)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
       ON CONFLICT (device_id, external_id) DO UPDATE SET
         address = excluded.address,
         normalized_address = excluded.normalized_address,
         contact_name = excluded.contact_name,
         direction = excluded.direction,
         call_type = excluded.call_type,
         answered = excluded.answered,
         duration_seconds = excluded.duration_seconds,
         started_at = excluded.started_at,
         service_provider = excluded.service_provider,
         synced_at = now()`,
      [
        draft.id,
        draft.deviceId,
        draft.externalId,
        draft.address,
        draft.normalizedAddress,
        draft.contactName,
        draft.direction,
        draft.callType,
        draft.answered,
        draft.durationSeconds,
        draft.startedAt,
        draft.serviceProvider,
      ],
    );
  } catch (err) {
    throw wrap(err, 'upsertCall');
  }
}

export async function listCalls(pool: Pool, filter: CallFilter = {}): Promise<readonly CallRecord[]> {
  try {
    const clauses: string[] = [];
    const params: unknown[] = [];

    if (filter.deviceId) {
      clauses.push(`device_id = $${params.length + 1}`);
      params.push(filter.deviceId);
    }
    if (filter.direction) {
      clauses.push(`direction = $${params.length + 1}`);
      params.push(filter.direction);
    }
    if (filter.callType) {
      clauses.push(`call_type = $${params.length + 1}`);
      params.push(filter.callType);
    }
    if (filter.since) {
      clauses.push(`started_at > $${params.length + 1}`);
      params.push(filter.since);
    }

    const where = clauses.length ? `WHERE ${clauses.join(' AND ')}` : '';
    const limit = filter.limit ?? 500;
    const offset = filter.offset ?? 0;

    const rows = await pgAll<Row>(
      pool,
      `SELECT * FROM macsync.calls ${where}
       ORDER BY COALESCE(started_at, synced_at) DESC
       LIMIT $${params.length + 1} OFFSET $${params.length + 2}`,
      [...params, limit, offset],
    );
    return rows.map(hydrate);
  } catch (err) {
    throw wrap(err, 'listCalls');
  }
}

export async function countCalls(pool: Pool, deviceId?: string): Promise<number> {
  try {
    if (deviceId) {
      const row = await pgGet<{ n: string }>(
        pool,
        'SELECT COUNT(*)::text as n FROM macsync.calls WHERE device_id = $1',
        [deviceId],
      );
      return Number(row?.n ?? 0);
    }
    const row = await pgGet<{ n: string }>(pool, 'SELECT COUNT(*)::text as n FROM macsync.calls');
    return Number(row?.n ?? 0);
  } catch (err) {
    throw wrap(err, 'countCalls');
  }
}

export async function getLastCallSync(pool: Pool, deviceId: string): Promise<string | null> {
  try {
    const row = await pgGet<{ last: string | null }>(
      pool,
      'SELECT MAX(synced_at) as last FROM macsync.calls WHERE device_id = $1',
      [deviceId],
    );
    return row?.last ?? null;
  } catch (err) {
    throw wrap(err, 'getLastCallSync');
  }
}
