import type { Channel, StaffTag, Ticket } from '@wa-ticketing/shared';
import { authService } from './authService.js';
import { channelService } from './channelService.js';
import { assertCapability } from './channelProviderRules.js';
import { contactService } from './contactService.js';
import { ticketService } from './ticketService.js';

export type GroupMemberView = {
  phone: string;
  display: string;
  kind: 'client' | 'staff' | 'unknown';
  user_id: string | null;
  staff_tags: StaffTag[];
  can_remove: boolean;
};

export type TicketGroupView = {
  ticket_id: string;
  wa_group_jid: string;
  subject: string;
  channel_id: string;
  channel_name: string;
  client: { contact_id: string; phone: string; display: string };
  members: GroupMemberView[];
  participant_count: number;
};

function digits(phone: string): string {
  return phone.replace(/\D/g, '');
}

function phonesMatch(a: string, b: string): boolean {
  const da = digits(a);
  const db = digits(b);
  if (!da || !db) return false;
  if (da === db) return true;
  const n = Math.min(da.length, db.length, 10);
  if (n < 8) return false;
  return da.slice(-n) === db.slice(-n);
}

/** Group ticket plus its channel; rejects providers without group support before any side effect. */
async function requireGroupTicket(ticketId: string): Promise<{ ticket: Ticket; channel: Channel }> {
  const ticket = await ticketService.getById(ticketId);
  if (!ticket) throw Object.assign(new Error('Ticket not found'), { statusCode: 404 });
  if (!ticket.wa_group_jid?.trim()) {
    throw Object.assign(new Error('Ticket is not a WhatsApp group ticket'), { statusCode: 400 });
  }
  const channel = await channelService.getById(ticket.channel_id);
  if (!channel) throw Object.assign(new Error('Channel not found'), { statusCode: 404 });
  assertCapability(channel, 'groups');
  return { ticket, channel };
}

export const ticketGroupService = {
  async getGroupView(ticketId: string): Promise<TicketGroupView> {
    const { ticket, channel } = await requireGroupTicket(ticketId);

    const contact = await contactService.getById(ticket.contact_id);
    const clientPhone = contact?.phone_number ?? '';
    const clientDisplay = contact?.display_name?.trim() || clientPhone;

    let meta: { subject: string; phones: string[]; participantCount: number };
    try {
      meta = await channelService.getGroupMetadata(ticket.channel_id, ticket.wa_group_jid!);
    } catch (err) {
      // Fallback to known staff + client if WA metadata unavailable
      meta = {
        subject: ticket.wa_group_subject ?? 'Group',
        phones: [],
        participantCount: 0,
      };
      void err;
    }

    const staffUsers = (await authService.listUsers()).filter(
      u => u.is_active && (u.role === 'admin' || u.role === 'user') && u.phone?.trim(),
    );
    const trackedIds = new Set(ticket.wa_group_participant_user_ids ?? []);

    const members: GroupMemberView[] = [];
    const seen = new Set<string>();

    const pushMember = (phoneRaw: string, forceKind?: GroupMemberView['kind']) => {
      const key = digits(phoneRaw);
      if (!key || seen.has(key)) return;
      seen.add(key);

      if (clientPhone && phonesMatch(phoneRaw, clientPhone)) {
        members.push({
          phone: contactService.normalize(clientPhone),
          display: clientDisplay,
          kind: 'client',
          user_id: null,
          staff_tags: [],
          can_remove: false,
        });
        return;
      }

      const staff = staffUsers.find(u => u.phone && phonesMatch(u.phone, phoneRaw));
      if (staff) {
        members.push({
          phone: contactService.normalize(staff.phone!),
          display: staff.display_name?.trim() || staff.email,
          kind: 'staff',
          user_id: staff.id,
          staff_tags: staff.staff_tags ?? [],
          can_remove: true,
        });
        trackedIds.add(staff.id);
        return;
      }

      members.push({
        phone: phoneRaw.startsWith('+') ? phoneRaw : `+${key}`,
        display: phoneRaw.startsWith('+') ? phoneRaw : `+${key}`,
        kind: forceKind ?? 'unknown',
        user_id: null,
        staff_tags: [],
        can_remove: false,
      });
    };

    if (clientPhone) pushMember(clientPhone, 'client');
    for (const p of meta.phones) pushMember(p);

    // Include tracked staff even if metadata missed them (LID / sync lag)
    for (const uid of ticket.wa_group_participant_user_ids ?? []) {
      const staff = staffUsers.find(u => u.id === uid);
      if (staff?.phone) pushMember(staff.phone);
    }

    return {
      ticket_id: ticket.id,
      wa_group_jid: ticket.wa_group_jid!,
      subject: meta.subject || ticket.wa_group_subject || 'Group',
      channel_id: channel.id,
      channel_name: channel.name,
      client: {
        contact_id: ticket.contact_id,
        phone: clientPhone,
        display: clientDisplay,
      },
      members,
      participant_count: meta.participantCount || members.length,
    };
  },

  async addStaff(ticketId: string, userId: string): Promise<TicketGroupView> {
    const { ticket } = await requireGroupTicket(ticketId);
    const user = await authService.getUser(userId);
    if (!user || (user.role !== 'admin' && user.role !== 'user') || !user.is_active) {
      throw Object.assign(new Error('Invalid staff user'), { statusCode: 400 });
    }
    if (!user.phone?.trim() || !contactService.isWhatsAppPhone(user.phone)) {
      throw Object.assign(new Error('Staff user has no WhatsApp phone'), { statusCode: 400 });
    }

    const phone = contactService.normalize(user.phone);
    await channelService.updateGroupParticipants(
      ticket.channel_id,
      ticket.wa_group_jid!,
      'add',
      [phone],
    );

    const ids = new Set(ticket.wa_group_participant_user_ids ?? []);
    ids.add(userId);
    await ticketService.setGroupParticipantUserIds(ticketId, [...ids]);

    return this.getGroupView(ticketId);
  },

  async removeStaff(ticketId: string, userId: string): Promise<TicketGroupView> {
    const { ticket } = await requireGroupTicket(ticketId);
    const contact = await contactService.getById(ticket.contact_id);
    const user = await authService.getUser(userId);
    if (!user?.phone?.trim()) {
      throw Object.assign(new Error('Staff user not found or has no phone'), { statusCode: 400 });
    }
    if (contact?.phone_number && phonesMatch(user.phone, contact.phone_number)) {
      throw Object.assign(new Error('Cannot remove the client from the group'), { statusCode: 400 });
    }

    const phone = contactService.normalize(user.phone);
    await channelService.updateGroupParticipants(
      ticket.channel_id,
      ticket.wa_group_jid!,
      'remove',
      [phone],
    );

    const ids = (ticket.wa_group_participant_user_ids ?? []).filter(id => id !== userId);
    await ticketService.setGroupParticipantUserIds(ticketId, ids);

    return this.getGroupView(ticketId);
  },
};
