import { sql } from '../db.js';
import type { CalendarRequest, Channel, Message, User } from '@wa-ticketing/shared';
import { auditService } from './auditService.js';
import { authService } from './authService.js';
import { calendarService } from './calendarService.js';
import { canStaffSendOnChannel } from './channelProviderRules.js';
import { channelService } from './channelService.js';
import { contactService } from './contactService.js';
import { messagingService } from './messagingService.js';
import { enrichMessageWithMediaUrl } from './ticketMediaService.js';
import { ticketService } from './ticketService.js';

export type StaffTicketRow = {
  id: string;
  status: string;
  priority: string;
  created_at: string;
  updated_at: string;
  last_message_at: string | null;
  wa_group_subject: string | null;
  contact_name: string | null;
  contact_phone: string | null;
  channel_name: string | null;
  assigned: boolean;
  group_member: boolean;
  whatsapp_thread: boolean;
};

export type StaffChatState = {
  open_ticket_id: string | null;
  channel_id: string | null;
  contact_id: string | null;
  phone: string | null;
  messages: Message[];
};

async function listRelatedTickets(userId: string, phone: string | null): Promise<StaffTicketRow[]> {
  const digits = phone ? contactService.normalize(phone).replace(/\D/g, '') : '';
  const rows = await sql<StaffTicketRow[]>`
    SELECT
      t.id, t.status::text AS status, t.priority::text AS priority,
      t.created_at, t.updated_at, t.wa_group_subject,
      (SELECT MAX(m.created_at) FROM messages m WHERE m.ticket_id = t.id) AS last_message_at,
      c.display_name AS contact_name,
      c.phone_number AS contact_phone,
      ch.name AS channel_name,
      (t.assigned_to = ${userId}) AS assigned,
      (${userId}::uuid = ANY (t.wa_group_participant_user_ids)) AS group_member,
      ${digits.length >= 8
        ? sql`(regexp_replace(COALESCE(c.phone_number, ''), '[^0-9]', '', 'g') = ${digits}
             AND (t.wa_group_jid IS NULL OR btrim(t.wa_group_jid) = ''))`
        : sql`FALSE`} AS whatsapp_thread
    FROM tickets t
    LEFT JOIN contacts c ON c.id = t.contact_id
    LEFT JOIN channels ch ON ch.id = t.channel_id
    WHERE t.assigned_to = ${userId}
       OR ${userId}::uuid = ANY (t.wa_group_participant_user_ids)
       ${digits.length >= 8
         ? sql`OR regexp_replace(c.phone_number, '[^0-9]', '', 'g') = ${digits}`
         : sql``}
    ORDER BY t.updated_at DESC
    LIMIT 80
  `;
  return rows;
}

/** Direct (non-group) WhatsApp history for this staff phone — inbound replies stay on this contact. */
async function loadChat(phone: string | null): Promise<StaffChatState> {
  const empty: StaffChatState = {
    open_ticket_id: null,
    channel_id: null,
    contact_id: null,
    phone: phone?.trim() || null,
    messages: [],
  };
  if (!phone?.trim() || !contactService.isWhatsAppPhone(phone)) return empty;

  const normalized = contactService.normalize(phone);
  const digits = normalized.replace(/\D/g, '');
  if (digits.length < 8) return empty;

  const [contact] = await sql<Array<{ id: string; phone_number: string }>>`
    SELECT id, phone_number FROM contacts
    WHERE regexp_replace(phone_number, '[^0-9]', '', 'g') = ${digits}
    LIMIT 1
  `;
  if (!contact) return { ...empty, phone: normalized };

  const open = await sql<Array<{ id: string; channel_id: string }>>`
    SELECT id, channel_id FROM tickets
    WHERE contact_id = ${contact.id}
      AND status = 'open'
      AND (wa_group_jid IS NULL OR btrim(wa_group_jid) = '')
    ORDER BY updated_at DESC
    LIMIT 1
  `;

  const rows = await sql<Message[]>`
    SELECT m.*
    FROM messages m
    JOIN tickets t ON t.id = m.ticket_id
    WHERE t.contact_id = ${contact.id}
      AND (t.wa_group_jid IS NULL OR btrim(t.wa_group_jid) = '')
    ORDER BY m.created_at ASC, m.sequence ASC
    LIMIT 500
  `;
  const messages = await Promise.all(rows.map(enrichMessageWithMediaUrl));

  return {
    open_ticket_id: open[0]?.id ?? null,
    channel_id: open[0]?.channel_id ?? null,
    contact_id: contact.id,
    phone: contact.phone_number,
    messages,
  };
}

async function ensureStaffContact(user: User) {
  if (!user.phone?.trim() || !contactService.isWhatsAppPhone(user.phone)) {
    throw Object.assign(new Error('Staff member has no WhatsApp phone on their profile'), { statusCode: 400 });
  }
  const label = user.display_name?.trim() || user.email;
  const contact = await contactService.findOrCreate(user.phone, label);
  if (!contact.display_name?.trim()) {
    const [updated] = await sql<[typeof contact]>`
      UPDATE contacts SET display_name = ${label}
      WHERE id = ${contact.id} AND (display_name IS NULL OR btrim(display_name) = '')
      RETURNING id, phone_number, display_name, company_id, created_at
    `;
    return updated ?? contact;
  }
  return contact;
}

export const staffWorkspaceService = {
  async get(id: string): Promise<{
    user: User;
    tickets: StaffTicketRow[];
    calendar: CalendarRequest[];
    activity: unknown[];
    chat: StaffChatState;
  } | null> {
    const user = await authService.getUser(id);
    if (!user || !authService.isStaffRole(user) || !user.is_active) return null;

    const [tickets, calendar, activity, chat] = await Promise.all([
      listRelatedTickets(id, user.phone),
      calendarService.listRequests({ assignedTo: id, limit: 40 }),
      auditService.query({ actorId: id, limit: 40 }),
      loadChat(user.phone),
    ]);

    return { user, tickets, calendar, activity, chat };
  },

  /**
   * Send WhatsApp to this staff phone via a connected channel.
   * Reuses the open direct ticket for the contact (so inbound replies stay in-thread).
   */
  async sendChat(
    staffUserId: string,
    channelId: string,
    content: string,
    actorId: string,
  ): Promise<{ ticket_id: string; message: Message; chat: StaffChatState }> {
    const user = await authService.getUser(staffUserId);
    if (!user || !authService.isStaffRole(user) || !user.is_active) {
      throw Object.assign(new Error('Staff member not found'), { statusCode: 404 });
    }

    const text = content.trim();
    if (!text) throw Object.assign(new Error('content is required'), { statusCode: 400 });

    const channel = await channelService.getById(channelId);
    if (!channel) throw Object.assign(new Error('Channel not found'), { statusCode: 404 });
    if (channel.kind !== 'whatsapp') {
      throw Object.assign(new Error('Only WhatsApp channels are supported'), { statusCode: 400 });
    }
    if (!canStaffSendOnChannel(channel as Channel)) {
      throw Object.assign(new Error('Channel is not connected'), { statusCode: 400 });
    }

    const contact = await ensureStaffContact(user);
    // Prefer an existing open 1:1 ticket so inbound WhatsApp replies stay on one thread
    let ticket = await ticketService.findOpenTicket(contact.id, channelId);
    if (!ticket) {
      const [anyOpen] = await sql<Array<{ id: string; channel_id: string }>>`
        SELECT id, channel_id FROM tickets
        WHERE contact_id = ${contact.id}
          AND status = 'open'
          AND (wa_group_jid IS NULL OR btrim(wa_group_jid) = '')
        ORDER BY updated_at DESC
        LIMIT 1
      `;
      if (anyOpen) {
        ticket = await ticketService.getById(anyOpen.id);
      }
    }
    if (!ticket) {
      ticket = await ticketService.createTicket(contact.id, channelId, { step: 'done', answers: {} }, {
        assignedTo: staffUserId,
        actorId,
      });
    }

    const sendChannelId = ticket.channel_id;
    const message = await messagingService.appendMessage(ticket.id, 'outbound', text, null, actorId);
    try {
      const waMessageId = await channelService.sendMessage(sendChannelId, contact.phone_number, text);
      if (waMessageId) {
        await sql`UPDATE messages SET wa_message_id = ${waMessageId} WHERE id = ${message.id}`;
        message.wa_message_id = waMessageId;
      }
      await auditService.log(actorId, 'user', 'message.sent', 'message', message.id, {
        channelId: sendChannelId,
        ticketId: ticket.id,
        staffUserId,
      });
    } catch (err) {
      await auditService.log(actorId, 'user', 'message.delivery_failed', 'message', message.id, {
        error: String(err),
        ticketId: ticket.id,
      });
      throw Object.assign(new Error(`WhatsApp send failed: ${String(err)}`), { statusCode: 502 });
    }

    const chat = await loadChat(user.phone);
    return { ticket_id: ticket.id, message, chat };
  },
};
