/**
 * Contact summary generator.
 *
 * Given a contact and their recent message history, produces a three-part
 * structured digest via model-boss:
 *   mostRecently  — 1 line: what they most recently discussed
 *   overallSummary — 1-3 sentences: relationship / communication style
 *   recap          — 1 sentence: who this person is
 *
 * The `chat` parameter is injectable for testing — pass a stub instead of the
 * real model-boss `chatJson` function.
 */

import type { Pool } from 'pg';
import { pgAll } from '@/shared/db';
import type { ContactSummaryData } from '@/entities/contact';

const SUMMARY_SCHEMA: Record<string, unknown> = {
  type: 'object',
  properties: {
    mostRecently: { type: 'string' },
    overallSummary: { type: 'string' },
    recap: { type: 'string' },
  },
  required: ['mostRecently', 'overallSummary', 'recap'],
  additionalProperties: false,
};

const SYSTEM_PROMPT = `You summarise a person based on iMessage conversation history.
Respond with a JSON object matching the schema. Be concise and specific — no filler phrases.

Fields:
- mostRecently: one sentence describing what was most recently discussed (start with a verb, e.g. "Planned a dinner…")
- overallSummary: 1–3 sentences describing the relationship, communication style, and recurring topics
- recap: one sentence identifying who this person is (role/relationship)`;

interface MessageRow {
  body: string;
  is_from_me: boolean;
  sent_at: string;
  sender_display_name: string | null;
}

export type ChatFn = (
  messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>,
  opts: {
    responseSchema?: Record<string, unknown>;
    schemaName?: string;
    parse: (raw: unknown) => ContactSummaryData;
    timeoutMs?: number;
  },
) => Promise<ContactSummaryData>;

function parseSummary(raw: unknown): ContactSummaryData {
  if (raw === null || typeof raw !== 'object') throw new Error('expected object');
  const obj = raw as Record<string, unknown>;
  if (typeof obj.mostRecently !== 'string') throw new Error('missing mostRecently');
  if (typeof obj.overallSummary !== 'string') throw new Error('missing overallSummary');
  if (typeof obj.recap !== 'string') throw new Error('missing recap');
  return {
    mostRecently: obj.mostRecently || null,
    overallSummary: obj.overallSummary || null,
    recap: obj.recap || null,
  };
}

async function fetchRecentMessages(
  pool: Pool,
  deviceId: string,
  handles: string[],
  limit = 30,
): Promise<MessageRow[]> {
  if (handles.length === 0) return [];

  const placeholders = handles.map((_, i) => `$${i + 3}`).join(', ');

  return pgAll<MessageRow>(
    pool,
    `SELECT m.body, m.is_from_me, m.sent_at, m.sender_display_name
     FROM macsync.messages m
     JOIN macsync.conversations c ON c.id = m.conversation_id
     WHERE c.device_id = $1
       AND m.body <> ''
       AND (
         m.from_handle IN (${placeholders})
         OR EXISTS (
           SELECT 1 FROM jsonb_array_elements_text(c.participants) p
           WHERE p IN (${placeholders})
         )
       )
     ORDER BY m.sent_at DESC
     LIMIT $2`,
    [deviceId, limit, ...handles],
  );
}

function formatMessages(rows: MessageRow[], contactName: string): string {
  if (rows.length === 0) return '(no messages available)';

  return rows
    .slice()
    .reverse()
    .map((r) => {
      const speaker = r.is_from_me ? 'Me' : (r.sender_display_name ?? contactName);
      const date = new Date(r.sent_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
      const body = r.body.slice(0, 300).replace(/\n+/g, ' ');
      return `[${date}] ${speaker}: ${body}`;
    })
    .join('\n');
}

export interface ContactInput {
  id: string;
  deviceId: string;
  handle: string;
  displayName: string;
  phoneNumbers: readonly string[];
  emails: readonly string[];
}

export async function generateContactSummary(
  pool: Pool,
  contact: ContactInput,
  chat: ChatFn,
): Promise<ContactSummaryData | null> {
  const handles = [contact.handle, ...contact.phoneNumbers, ...contact.emails].filter(
    (h, i, arr) => Boolean(h) && arr.indexOf(h) === i,
  );

  let rows: MessageRow[];
  try {
    rows = await fetchRecentMessages(pool, contact.deviceId, handles);
  } catch (err) {
    throw new Error(
      `generateContactSummary: failed to fetch messages for ${contact.id}: ${err instanceof Error ? err.message : String(err)}`,
      { cause: err },
    );
  }

  if (rows.length === 0) {
    return { mostRecently: null, overallSummary: null, recap: null };
  }

  const transcript = formatMessages(rows, contact.displayName || contact.handle);
  const userMessage = `Contact: ${contact.displayName || contact.handle} (${contact.handle})\n\nRecent messages (oldest first):\n${transcript}`;

  try {
    return await chat(
      [
        { role: 'system', content: SYSTEM_PROMPT },
        { role: 'user', content: userMessage },
      ],
      {
        responseSchema: SUMMARY_SCHEMA,
        schemaName: 'contact_summary',
        parse: parseSummary,
        timeoutMs: 45_000,
      },
    );
  } catch (err) {
    throw new Error(
      `generateContactSummary: model-boss call failed for ${contact.id}: ${err instanceof Error ? err.message : String(err)}`,
      { cause: err },
    );
  }
}
