import { sql } from '../db.js';
import type { Message, MessageDirection, MessageSource, MessageContentType } from '@wa-ticketing/shared';
import { auditService } from './auditService.js';
import { maybeAutoSyncFirstEngagement } from './integrations/ticketAutoSync.js';

/** Optional media fields for image / voice_note messages */
export type AppendMessageMedia = {
  messageType: 'image' | 'voice_note' | 'document';
  mediaPath: string;
  mediaMime: string;
  mediaSizeBytes: number;
  mediaDurationSec?: number | null;
  mediaCaption?: string | null;
};

function mapMessageRow(row: Message): Message {
  return {
    ...row,
    message_type: (row.message_type ?? 'text') as MessageContentType,
    media_path: row.media_path ?? null,
    media_mime: row.media_mime ?? null,
    media_size_bytes: row.media_size_bytes != null ? Number(row.media_size_bytes) : null,
    media_duration_sec: row.media_duration_sec != null ? Number(row.media_duration_sec) : null,
    media_caption: row.media_caption ?? null,
    from_phone: row.from_phone ?? null,
  };
}

export const messagingService = {
  async appendMessage(
    ticketId: string,
    direction: MessageDirection,
    content: string,
    waMessageId: string | null,
    actorId?: string,
    source: MessageSource = 'whatsapp',
    media?: AppendMessageMedia | null,
    fromPhone?: string | null,
  ): Promise<Message> {
    const [{ next_seq }] = await sql<[{ next_seq: number }]>`
      SELECT COALESCE(MAX(sequence), 0) + 1 AS next_seq
      FROM messages WHERE ticket_id = ${ticketId}
    `;

    const messageType: MessageContentType = media ? media.messageType : 'text';

    const [message] = await sql<[Message]>`
      INSERT INTO messages (
        ticket_id, direction, content, wa_message_id, sequence, source,
        message_type, media_path, media_mime, media_size_bytes, media_duration_sec, media_caption,
        from_phone
      )
      VALUES (
        ${ticketId},
        ${direction},
        ${content},
        ${waMessageId},
        ${next_seq},
        ${source},
        ${messageType},
        ${media?.mediaPath ?? null},
        ${media?.mediaMime ?? null},
        ${media?.mediaSizeBytes ?? null},
        ${media?.mediaDurationSec ?? null},
        ${media?.mediaCaption ?? null},
        ${fromPhone ?? null}
      )
      RETURNING *
    `;

    const mapped = mapMessageRow(message);

    if (direction === 'outbound' && actorId) {
      await auditService.log(actorId, 'user', 'message.sent', 'message', mapped.id, { ticketId });
    }

    // First engagement only — later messages on the same ticket do not re-sync.
    if (next_seq === 1) {
      void maybeAutoSyncFirstEngagement(ticketId, actorId ?? null);
    }

    return mapped;
  },

  /** Replace placeholder content (e.g. voice note transcript after Whisper). */
  async updateContent(messageId: string, content: string): Promise<Message | null> {
    const [row] = await sql<[Message]>`
      UPDATE messages SET content = ${content} WHERE id = ${messageId} RETURNING *
    `;
    return row ? mapMessageRow(row) : null;
  },

  async getMessages(ticketId: string): Promise<Message[]> {
    const rows = await sql<Message[]>`
      SELECT * FROM messages
      WHERE ticket_id = ${ticketId}
      ORDER BY sequence ASC
    `;
    return rows.map(mapMessageRow);
  },

  /** Most recent outbound on the ticket before `beforeSequence` (if any). */
  async getLastOutboundBeforeSequence(
    ticketId: string,
    beforeSequence: number,
  ): Promise<Message | null> {
    const [row] = await sql<[Message]>`
      SELECT * FROM messages
      WHERE ticket_id = ${ticketId}
        AND sequence < ${beforeSequence}
        AND direction = 'outbound'
      ORDER BY sequence DESC
      LIMIT 1
    `;
    return row ? mapMessageRow(row) : null;
  },

  async isDuplicate(waMessageId: string): Promise<boolean> {
    const [row] = await sql<[{ count: string }]>`
      SELECT COUNT(*) AS count FROM messages WHERE wa_message_id = ${waMessageId}
    `;
    return parseInt(row.count) > 0;
  },
};
