import { prisma } from '../../../config/database';
import { Prisma } from '@prisma/client';
import { smsQueueService } from '../../../services/sms-queue.service';

/**
 * Normalize a phone number: strip non-digit characters, validate 10-11 digits.
 */
function normalizePhone(raw: string): string | null {
  const digits = raw.replace(/\D/g, '');
  if (digits.length === 10) return digits;
  if (digits.length === 11 && digits.startsWith('1')) return digits;
  return null;
}

export interface ContactSearchResult {
  phone: string;
  name: string | null;
  source: 'sms_contact' | 'crm_contact' | 'conversation';
  sourceId: string;
  contactId?: string;
}

export const smsConversationsService = {
  async findAll(options: {
    page?: number;
    limit?: number;
    search?: string;
    status?: string;
    campaignId?: string;
    unreadOnly?: boolean;
  } = {}) {
    const { page = 1, limit = 50, search, status, campaignId, unreadOnly } = options;
    const skip = (page - 1) * limit;

    const where: Prisma.SmsConversationWhereInput = {};
    if (status) where.status = status as Prisma.EnumSmsConversationStatusFilter;
    if (campaignId) where.campaignId = campaignId;
    if (unreadOnly) where.unreadCount = { gt: 0 };
    if (search) {
      where.OR = [
        { phone: { contains: search } },
        { contactName: { contains: search, mode: 'insensitive' } },
      ];
    }

    const [items, total] = await Promise.all([
      prisma.smsConversation.findMany({
        where,
        orderBy: { lastMessageAt: 'desc' },
        skip,
        take: limit,
        include: {
          campaign: { select: { id: true, name: true } },
        },
      }),
      prisma.smsConversation.count({ where }),
    ]);

    return { items, total, page, limit };
  },

  async findById(id: string) {
    return prisma.smsConversation.findUnique({
      where: { id },
      include: {
        campaign: { select: { id: true, name: true } },
        contact: { select: { id: true, displayName: true } },
        messages: {
          orderBy: { sentAt: 'asc' },
          take: 200,
        },
      },
    });
  },

  async markRead(id: string) {
    // Mark conversation unread count to 0
    await prisma.smsConversation.update({
      where: { id },
      data: { unreadCount: 0 },
    });

    // Mark all unread messages in this conversation as read
    await prisma.smsMessage.updateMany({
      where: { conversationId: id, isRead: false },
      data: { isRead: true },
    });
  },

  async updateNotes(id: string, notes: string) {
    return prisma.smsConversation.update({
      where: { id },
      data: { notes },
    });
  },

  async updateTags(id: string, tags: string[]) {
    return prisma.smsConversation.update({
      where: { id },
      data: { tags: tags as unknown as Prisma.InputJsonValue },
    });
  },

  /**
   * Reply to a conversation (queues via BullMQ for rate limiting)
   */
  async reply(id: string, message: string) {
    const conversation = await prisma.smsConversation.findUnique({
      where: { id },
      select: { phone: true, campaignId: true, status: true },
    });
    if (!conversation) throw new Error('Conversation not found');
    if (conversation.status === 'OPTED_OUT') throw new Error('Cannot reply to opted-out conversation');

    // Create outbound message
    const smsMessage = await prisma.smsMessage.create({
      data: {
        phone: conversation.phone,
        message,
        direction: 'OUTBOUND',
        status: 'PENDING',
        connectionType: 'termux',
        campaignId: conversation.campaignId,
        conversationId: id,
      },
    });

    // Queue the SMS send
    await smsQueueService.addSmsJob({
      recipientId: smsMessage.id, // Use message ID as recipient ID for ad-hoc replies
      campaignId: conversation.campaignId || '',
      phone: conversation.phone,
      message,
      attemptNumber: 1,
    });

    // Update conversation stats
    await prisma.smsConversation.update({
      where: { id },
      data: {
        totalMessages: { increment: 1 },
        lastMessageAt: new Date(),
      },
    });

    return smsMessage;
  },

  async getStats() {
    const [total, active, optedOut, unread] = await Promise.all([
      prisma.smsConversation.count(),
      prisma.smsConversation.count({ where: { status: 'ACTIVE' } }),
      prisma.smsConversation.count({ where: { status: 'OPTED_OUT' } }),
      prisma.smsConversation.count({ where: { unreadCount: { gt: 0 } } }),
    ]);
    return { total, active, optedOut, unread };
  },

  /**
   * Bulk mark conversations as read
   */
  async bulkMarkRead(ids: string[]) {
    if (ids.length === 0) return { updated: 0 };

    const result = await prisma.smsConversation.updateMany({
      where: { id: { in: ids } },
      data: { unreadCount: 0 },
    });

    // Also mark all messages in these conversations as read
    await prisma.smsMessage.updateMany({
      where: { conversationId: { in: ids }, isRead: false },
      data: { isRead: true },
    });

    return { updated: result.count };
  },

  /**
   * Bulk close conversations
   */
  async bulkClose(ids: string[]) {
    if (ids.length === 0) return { updated: 0 };

    const result = await prisma.smsConversation.updateMany({
      where: { id: { in: ids }, status: 'ACTIVE' },
      data: { status: 'CLOSED' },
    });

    return { updated: result.count };
  },

  /**
   * Search contacts across SMS lists, CRM contacts, and existing conversations.
   * Deduplicates by phone number, prioritizing SMS contacts > CRM > conversations.
   */
  async searchContacts(query: string, limit = 20): Promise<ContactSearchResult[]> {
    const seen = new Map<string, ContactSearchResult>();

    const [smsEntries, crmContacts, existingConvs] = await Promise.all([
      // 1. SMS Contact List Entries
      prisma.smsContactListEntry.findMany({
        where: {
          OR: [
            { phone: { contains: query } },
            { name: { contains: query, mode: 'insensitive' } },
          ],
        },
        take: limit,
        select: { id: true, phone: true, name: true },
      }),

      // 2. CRM Contacts (with phones) — exclude opted out
      prisma.contact.findMany({
        where: {
          doNotContact: false,
          smsOptOut: false,
          OR: [
            { displayName: { contains: query, mode: 'insensitive' } },
            { phone: { contains: query } },
            { phones: { some: { phone: { contains: query } } } },
          ],
        },
        take: limit,
        select: {
          id: true,
          displayName: true,
          phone: true,
          phones: { select: { phone: true }, take: 5 },
        },
      }),

      // 3. Existing conversations
      prisma.smsConversation.findMany({
        where: {
          OR: [
            { phone: { contains: query } },
            { contactName: { contains: query, mode: 'insensitive' } },
          ],
        },
        take: limit,
        select: { id: true, phone: true, contactName: true, contactId: true, status: true },
      }),
    ]);

    // Add SMS contacts first (highest priority)
    for (const entry of smsEntries) {
      if (!seen.has(entry.phone)) {
        seen.set(entry.phone, {
          phone: entry.phone,
          name: entry.name,
          source: 'sms_contact',
          sourceId: entry.id,
        });
      }
    }

    // Add CRM contacts
    for (const contact of crmContacts) {
      const phones: string[] = [];
      if (contact.phone) phones.push(contact.phone);
      for (const cp of contact.phones) {
        if (!phones.includes(cp.phone)) phones.push(cp.phone);
      }
      for (const phone of phones) {
        if (!seen.has(phone)) {
          seen.set(phone, {
            phone,
            name: contact.displayName,
            source: 'crm_contact',
            sourceId: contact.id,
            contactId: contact.id,
          });
        }
      }
    }

    // Add existing conversations (lowest priority)
    for (const conv of existingConvs) {
      if (!seen.has(conv.phone)) {
        seen.set(conv.phone, {
          phone: conv.phone,
          name: conv.contactName,
          source: 'conversation',
          sourceId: conv.id,
          contactId: conv.contactId || undefined,
        });
      }
    }

    return Array.from(seen.values()).slice(0, limit);
  },

  /**
   * Start a new ad-hoc conversation or reuse an existing one, then send the first message.
   */
  async startConversation(input: {
    phone: string;
    message: string;
    contactName?: string;
    contactId?: string;
  }) {
    const normalized = normalizePhone(input.phone);
    if (!normalized) throw Object.assign(new Error('Invalid phone number'), { statusCode: 400 });

    // Use a transaction to prevent race conditions on conversation lookup/create
    const conversation = await prisma.$transaction(async (tx) => {
      // Look for existing ad-hoc conversation (campaignId = null)
      const existing = await tx.smsConversation.findFirst({
        where: { phone: normalized, campaignId: null },
      });

      if (existing) {
        if (existing.status === 'OPTED_OUT') {
          throw Object.assign(new Error('Cannot message opted-out contact'), { statusCode: 409 });
        }

        // Reopen if closed, update stats
        return tx.smsConversation.update({
          where: { id: existing.id },
          data: {
            status: 'ACTIVE',
            totalMessages: { increment: 1 },
            lastMessageAt: new Date(),
            // Update contact info if provided and not already set
            contactName: existing.contactName || input.contactName || undefined,
            contactId: existing.contactId || input.contactId || undefined,
          },
        });
      }

      // Create new conversation
      return tx.smsConversation.create({
        data: {
          phone: normalized,
          contactName: input.contactName || null,
          contactId: input.contactId || null,
          status: 'ACTIVE',
          totalMessages: 1,
          lastMessageAt: new Date(),
        },
      });
    });

    // Create outbound message
    const smsMessage = await prisma.smsMessage.create({
      data: {
        phone: normalized,
        message: input.message,
        direction: 'OUTBOUND',
        status: 'PENDING',
        connectionType: 'termux',
        conversationId: conversation.id,
      },
    });

    // Queue the SMS send
    await smsQueueService.addSmsJob({
      recipientId: smsMessage.id,
      campaignId: '',
      phone: normalized,
      message: input.message,
      attemptNumber: 1,
    });

    // Return full conversation with messages
    return this.findById(conversation.id);
  },
};
