import { readFile } from 'fs/promises';
import type { Pool } from 'pg';
import { pgAll, pgGet } from '@/shared/db';
import { upsertProspect, listHandlesForDevice } from '@/entities/prospect';
import type { Prospect, ProspectTier, ProspectHeat } from '@/entities/prospect';

export { getProspect, listProspects } from '@/entities/prospect';

// prettier-ignore
const LA_AREA_CODES  = new Set(['213','310','323','424','442','562','626','657','661','714','747','760','818','909','951']);
const NYC_AREA_CODES = new Set(['212','718','917','347','646','929','516','914']);
const SF_AREA_CODES  = new Set(['415','628','408','510','650','669','925','707']);
const CHI_AREA_CODES = new Set(['312','773','872','630','847','815','779','331']);

// Matches explicit rate quotes Quinn uses: $700, $750, 700/hr, 750/hr, etc.
const RATE_RE = /\$(?:700|750|800|850|900)|(?:700|750|800|850|900)\s*\/?\s*hr/i;

function metroFromHandle(handle: string): string | null {
  const digits = handle.replace(/\D/g, '');
  let area: string | null = null;
  if (digits.length === 10) area = digits.slice(0, 3);
  else if (digits.length === 11 && digits[0] === '1') area = digits.slice(1, 4);
  if (!area) return null;
  if (LA_AREA_CODES.has(area))  return 'LA';
  if (NYC_AREA_CODES.has(area)) return 'NYC';
  if (SF_AREA_CODES.has(area))  return 'SF';
  if (CHI_AREA_CODES.has(area)) return 'CHI';
  return null;
}

function computeHeat(lastInboundAt: string | null): ProspectHeat {
  if (!lastInboundAt) return 'DEAD';
  const days = (Date.now() - new Date(lastInboundAt).getTime()) / 86_400_000;
  if (days < 7)  return 'HOT';
  if (days < 30) return 'WARM';
  if (days < 90) return 'COLD';
  return 'DEAD';
}

async function loadBlockList(path: string | undefined): Promise<Set<string>> {
  if (!path) return new Set();
  try {
    const raw = await readFile(path, 'utf8');
    const parsed: unknown = JSON.parse(raw);
    return new Set(Array.isArray(parsed) ? (parsed as string[]) : []);
  } catch {
    return new Set();
  }
}

async function loadVipRoster(url: string | undefined, token: string): Promise<Set<string>> {
  if (!url) return new Set();
  try {
    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${token}` },
      signal: AbortSignal.timeout(5000),
    });
    if (!res.ok) return new Set();
    const data = (await res.json()) as { handles?: string[] };
    return new Set(Array.isArray(data.handles) ? data.handles : []);
  } catch {
    return new Set();
  }
}

interface MsgRow {
  is_from_me: boolean;
  body: string;
  sent_at: string;
}

// v0 heuristic: outbound rate quote that got a response within 24h counts as a booking signal.
// ≥2 signals ≥14d apart → REGULAR.
async function detectRegular(pool: Pool, deviceId: string, handle: string): Promise<boolean> {
  const rows = await pgAll<MsgRow>(
    pool,
    `SELECT m.is_from_me, m.body, m.sent_at
     FROM macsync.messages m
     JOIN macsync.conversations c ON c.id = m.conversation_id
     WHERE m.device_id = $1 AND c.device_id = $1
       AND $2 = ANY(c.participants) AND c.is_group = false
     ORDER BY m.sent_at ASC`,
    [deviceId, handle],
  );

  const events: Date[] = [];
  for (let i = 0; i < rows.length; i++) {
    const msg = rows[i]!;
    if (!msg.is_from_me || !RATE_RE.test(msg.body)) continue;
    const outAt = new Date(msg.sent_at);
    const cutoff = new Date(outAt.getTime() + 86_400_000);
    const responded = rows.slice(i + 1).some((m) => !m.is_from_me && new Date(m.sent_at) <= cutoff);
    if (responded) events.push(outAt);
  }

  if (events.length < 2) return false;
  for (let i = 1; i < events.length; i++) {
    if ((events[i]!.getTime() - events[0]!.getTime()) / 86_400_000 >= 14) return true;
  }
  return false;
}

interface AggRow {
  msg_count_in: string;
  msg_count_out: string;
  first_contact_at: string | null;
  last_contact_at: string | null;
  last_inbound_at: string | null;
  last_outbound_at: string | null;
}

export async function classifyHandle(pool: Pool, deviceId: string, handle: string): Promise<Prospect> {
  const blockListPath = process.env['BLOCK_LIST_PATH'];
  const vipRosterUrl  = process.env['VIP_ROSTER_URL'];
  const serviceToken  = process.env['SERVICE_TOKEN'] ?? '';

  const [blockList, vipRoster, aggRows, contactRow] = await Promise.all([
    loadBlockList(blockListPath),
    loadVipRoster(vipRosterUrl, serviceToken),
    pgAll<AggRow>(
      pool,
      `SELECT
         COUNT(*) FILTER (WHERE NOT m.is_from_me) AS msg_count_in,
         COUNT(*) FILTER (WHERE m.is_from_me)     AS msg_count_out,
         MIN(m.sent_at)                            AS first_contact_at,
         MAX(m.sent_at)                            AS last_contact_at,
         MAX(m.sent_at) FILTER (WHERE NOT m.is_from_me) AS last_inbound_at,
         MAX(m.sent_at) FILTER (WHERE m.is_from_me)     AS last_outbound_at
       FROM macsync.messages m
       JOIN macsync.conversations c ON c.id = m.conversation_id
       WHERE m.device_id = $1 AND c.device_id = $1
         AND $2 = ANY(c.participants) AND c.is_group = false`,
      [deviceId, handle],
    ),
    pgGet<{ handle: string }>(
      pool,
      'SELECT handle FROM macsync.contacts WHERE device_id = $1 AND handle = $2 LIMIT 1',
      [deviceId, handle],
    ),
  ]);

  const agg = aggRows[0] ?? {
    msg_count_in: '0', msg_count_out: '0',
    first_contact_at: null, last_contact_at: null,
    last_inbound_at: null, last_outbound_at: null,
  };
  const hasContact = contactRow !== null;
  const isBlocked  = blockList.has(handle);
  const isVip      = vipRoster.has(handle);

  let tier: ProspectTier;
  if (isBlocked) {
    tier = 'BLOCK';
  } else if (isVip) {
    tier = 'VIP';
  } else if (hasContact) {
    tier = 'PEER';
  } else if (await detectRegular(pool, deviceId, handle)) {
    tier = 'REGULAR';
  } else {
    tier = 'PROSPECT';
  }

  const prospect: Prospect = {
    handle,
    deviceId,
    tier,
    heat:           computeHeat(agg.last_inbound_at),
    metro:          metroFromHandle(handle),
    firstContactAt: agg.first_contact_at,
    lastContactAt:  agg.last_contact_at,
    lastInboundAt:  agg.last_inbound_at,
    lastOutboundAt: agg.last_outbound_at,
    msgCountIn:     Number(agg.msg_count_in),
    msgCountOut:    Number(agg.msg_count_out),
    rateBalkCount:  0,
    vipFlag:        isVip,
    peerFlag:       tier === 'PEER',
    regularFlag:    tier === 'REGULAR',
    updatedAt:      new Date().toISOString(),
  };

  await upsertProspect(pool, prospect);
  return prospect;
}

export async function reclassifyDevice(pool: Pool, deviceId: string): Promise<number> {
  const handles = await listHandlesForDevice(pool, deviceId);
  for (const handle of handles) {
    await classifyHandle(pool, deviceId, handle);
  }
  return handles.length;
}
