import { prisma } from '../../../config/database';
import { Prisma, type UserRole, type SupportLevel, type SmsResponseType } from '@prisma/client';
import { termuxClient } from '../../../services/termux.client';
import type { CreateContactListInput, UpdateContactListInput, CreateContactEntryInput } from './sms-contacts.schemas';

/**
 * Normalize a phone number: strip non-digit characters, validate 10-11 digits.
 * Returns null if invalid.
 */
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;
}

/**
 * Parse CSV text into rows of objects. Supports comma and tab delimiters.
 * First row is treated as headers.
 */
function parseCsv(text: string): Record<string, string>[] {
  const lines = text.split(/\r?\n/).filter((l) => l.trim());
  if (lines.length < 2) return [];

  // Detect delimiter (tab vs comma)
  const delimiter = lines[0].includes('\t') ? '\t' : ',';
  const headers = lines[0].split(delimiter).map((h) => h.trim().replace(/^["']|["']$/g, ''));
  const rows: Record<string, string>[] = [];

  for (let i = 1; i < lines.length; i++) {
    const values = lines[i].split(delimiter).map((v) => v.trim().replace(/^["']|["']$/g, ''));
    const row: Record<string, string> = {};
    headers.forEach((h, idx) => {
      row[h] = values[idx] || '';
    });
    rows.push(row);
  }

  return rows;
}

/**
 * Detect which CSV column contains phone numbers by header name.
 */
function detectPhoneColumn(headers: string[]): string | null {
  const patterns = ['phone', 'mobile', 'cell', 'telephone', 'tel', 'number', 'sms'];
  const lower = headers.map((h) => h.toLowerCase());
  for (const pattern of patterns) {
    const idx = lower.findIndex((h) => h.includes(pattern));
    if (idx >= 0) return headers[idx];
  }
  return null;
}

/**
 * Detect which CSV column contains names by header name.
 */
function detectNameColumn(headers: string[]): string | null {
  const patterns = ['name', 'full_name', 'fullname', 'contact', 'first_name'];
  const lower = headers.map((h) => h.toLowerCase());
  for (const pattern of patterns) {
    const idx = lower.findIndex((h) => h.includes(pattern));
    if (idx >= 0) return headers[idx];
  }
  return null;
}

/**
 * Detect which CSV column contains email addresses by header name.
 */
function detectEmailColumn(headers: string[]): string | null {
  const patterns = ['email', 'e-mail', 'mail'];
  const lower = headers.map((h) => h.toLowerCase());
  for (const pattern of patterns) {
    const idx = lower.findIndex((h) => h.includes(pattern));
    if (idx >= 0) return headers[idx];
  }
  return null;
}

export const smsContactsService = {
  async createList(data: CreateContactListInput, userId?: string) {
    return prisma.smsContactList.create({
      data: {
        name: data.name,
        createdByUserId: userId,
      },
    });
  },

  async findAll(page = 1, limit = 50) {
    const skip = (page - 1) * limit;
    const [items, total] = await Promise.all([
      prisma.smsContactList.findMany({
        where: { status: 'ACTIVE' },
        orderBy: { createdAt: 'desc' },
        skip,
        take: limit,
        include: {
          createdByUser: { select: { id: true, name: true, email: true } },
          _count: { select: { entries: true, campaigns: true } },
        },
      }),
      prisma.smsContactList.count({ where: { status: 'ACTIVE' } }),
    ]);
    return { items, total, page, limit };
  },

  async findById(id: string) {
    return prisma.smsContactList.findUnique({
      where: { id },
      include: {
        createdByUser: { select: { id: true, name: true, email: true } },
        _count: { select: { entries: true, campaigns: true } },
      },
    });
  },

  async getAllEntries(page = 1, limit = 50, listId?: string, search?: string) {
    const skip = (page - 1) * limit;
    const where: Prisma.SmsContactListEntryWhereInput = {
      list: { status: 'ACTIVE' },
    };
    if (listId) where.listId = listId;
    if (search) {
      where.OR = [
        { phone: { contains: search } },
        { name: { contains: search, mode: 'insensitive' } },
        { email: { contains: search, mode: 'insensitive' } },
      ];
    }
    const [items, total] = await Promise.all([
      prisma.smsContactListEntry.findMany({
        where,
        orderBy: { createdAt: 'desc' },
        skip,
        take: limit,
        include: { list: { select: { id: true, name: true } } },
      }),
      prisma.smsContactListEntry.count({ where }),
    ]);
    return { items, total, page, limit };
  },

  async getEntries(listId: string, page = 1, limit = 100) {
    const skip = (page - 1) * limit;
    const [items, total] = await Promise.all([
      prisma.smsContactListEntry.findMany({
        where: { listId },
        orderBy: { createdAt: 'desc' },
        skip,
        take: limit,
      }),
      prisma.smsContactListEntry.count({ where: { listId } }),
    ]);
    return { items, total, page, limit };
  },

  async updateList(id: string, data: UpdateContactListInput) {
    return prisma.smsContactList.update({
      where: { id },
      data,
    });
  },

  async archiveList(id: string) {
    return prisma.smsContactList.update({
      where: { id },
      data: { status: 'ARCHIVED' },
    });
  },

  async addEntry(listId: string, data: CreateContactEntryInput) {
    const phone = normalizePhone(data.phone);
    if (!phone) throw new Error('Invalid phone number');

    const entry = await prisma.smsContactListEntry.upsert({
      where: { listId_phone: { listId, phone } },
      create: {
        listId,
        phone,
        name: data.name,
        email: data.email,
        customFields: data.customFields as Prisma.InputJsonValue,
      },
      update: {
        name: data.name,
        email: data.email,
        customFields: data.customFields as Prisma.InputJsonValue,
      },
    });

    // Update total count
    const count = await prisma.smsContactListEntry.count({ where: { listId } });
    await prisma.smsContactList.update({ where: { id: listId }, data: { totalContacts: count } });

    return entry;
  },

  async addEntriesBulk(listId: string, entries: { phone: string; name?: string; email?: string }[]) {
    let imported = 0;
    let skipped = 0;

    for (const entry of entries) {
      const phone = normalizePhone(entry.phone);
      if (!phone) { skipped++; continue; }

      try {
        await prisma.smsContactListEntry.upsert({
          where: { listId_phone: { listId, phone } },
          create: { listId, phone, name: entry.name, email: entry.email },
          update: { name: entry.name, email: entry.email },
        });
        imported++;
      } catch {
        skipped++;
      }
    }

    const total = await prisma.smsContactListEntry.count({ where: { listId } });
    await prisma.smsContactList.update({ where: { id: listId }, data: { totalContacts: total } });

    return { imported, skipped, total };
  },

  async deleteEntry(id: string) {
    const entry = await prisma.smsContactListEntry.delete({ where: { id } });
    // Update total count
    const count = await prisma.smsContactListEntry.count({ where: { listId: entry.listId } });
    await prisma.smsContactList.update({ where: { id: entry.listId }, data: { totalContacts: count } });
    return entry;
  },

  /**
   * Import contacts from CSV text into a list.
   * Normalizes phone numbers, deduplicates by phone within the list.
   */
  async importCsv(listId: string, csvText: string, filename?: string) {
    const rows = parseCsv(csvText);
    if (rows.length === 0) throw new Error('CSV file is empty or has no data rows');

    const headers = Object.keys(rows[0]);
    const phoneCol = detectPhoneColumn(headers);
    if (!phoneCol) throw new Error('Could not detect phone column in CSV. Expected a column named "phone", "mobile", "cell", etc.');

    const nameCol = detectNameColumn(headers);
    const emailCol = detectEmailColumn(headers);

    // Columns that aren't phone/name/email become custom fields
    const customCols = headers.filter((h) => h !== phoneCol && h !== nameCol && h !== emailCol);

    let imported = 0;
    let skipped = 0;
    let duplicates = 0;

    for (const row of rows) {
      const rawPhone = row[phoneCol];
      if (!rawPhone) { skipped++; continue; }

      const phone = normalizePhone(rawPhone);
      if (!phone) { skipped++; continue; }

      const customFields: Record<string, string> = {};
      for (const col of customCols) {
        if (row[col]) customFields[col] = row[col];
      }

      try {
        await prisma.smsContactListEntry.upsert({
          where: { listId_phone: { listId, phone } },
          create: {
            listId,
            phone,
            name: nameCol ? row[nameCol] || undefined : undefined,
            email: emailCol ? row[emailCol] || undefined : undefined,
            customFields: Object.keys(customFields).length > 0 ? customFields as unknown as Prisma.InputJsonValue : undefined,
          },
          update: {
            name: nameCol ? row[nameCol] || undefined : undefined,
            email: emailCol ? row[emailCol] || undefined : undefined,
            customFields: Object.keys(customFields).length > 0 ? customFields as unknown as Prisma.InputJsonValue : undefined,
          },
        });
        imported++;
      } catch {
        duplicates++;
      }
    }

    // Update total count + filename
    const count = await prisma.smsContactListEntry.count({ where: { listId } });
    await prisma.smsContactList.update({
      where: { id: listId },
      data: {
        totalContacts: count,
        ...(filename ? { originalFilename: filename } : {}),
      },
    });

    return { imported, skipped, duplicates, total: count };
  },

  /**
   * Import contacts from the phone's address book via Termux API
   */
  async importFromPhone(listId: string) {
    const contacts = await termuxClient.getContacts();
    if (contacts.length === 0) throw new Error('No contacts returned from phone');

    let imported = 0;
    let skipped = 0;

    for (const contact of contacts) {
      if (!contact.number) { skipped++; continue; }

      const phone = normalizePhone(contact.number);
      if (!phone) { skipped++; continue; }

      try {
        await prisma.smsContactListEntry.upsert({
          where: { listId_phone: { listId, phone } },
          create: { listId, phone, name: contact.name || undefined },
          update: { name: contact.name || undefined },
        });
        imported++;
      } catch {
        skipped++;
      }
    }

    const count = await prisma.smsContactListEntry.count({ where: { listId } });
    await prisma.smsContactList.update({ where: { id: listId }, data: { totalContacts: count } });

    return { imported, skipped, total: count };
  },

  /**
   * Find and count duplicate phone numbers within a list
   */
  async deduplicateList(listId: string) {
    // Since we have a unique constraint on [listId, phone], true duplicates
    // shouldn't exist. This method counts entries and returns stats.
    const count = await prisma.smsContactListEntry.count({ where: { listId } });
    return { totalEntries: count, duplicatesRemoved: 0 };
  },

  // =========================================================================
  // Database Import Sources — preview + import methods
  // =========================================================================

  /**
   * Preview users with phone numbers matching filters
   */
  async previewUsers(filters: { role?: string }) {
    const where: Prisma.UserWhereInput = { phone: { not: null } };
    if (filters.role) where.role = filters.role as UserRole;

    const [total, sample] = await Promise.all([
      prisma.user.count({ where }),
      prisma.user.findMany({
        where,
        select: { phone: true, name: true },
        take: 10,
      }),
    ]);

    return {
      total,
      sample: sample.map((u) => ({ phone: u.phone!, name: u.name || undefined })),
    };
  },

  /**
   * Import users with phone numbers into a contact list
   */
  async importFromUsers(listId: string, filters: { role?: string }) {
    const where: Prisma.UserWhereInput = { phone: { not: null } };
    if (filters.role) where.role = filters.role as UserRole;

    const users = await prisma.user.findMany({
      where,
      select: { phone: true, name: true, email: true },
    });

    let imported = 0;
    let skipped = 0;

    for (const user of users) {
      const phone = normalizePhone(user.phone!);
      if (!phone) { skipped++; continue; }

      try {
        await prisma.smsContactListEntry.upsert({
          where: { listId_phone: { listId, phone } },
          create: { listId, phone, name: user.name || undefined, email: user.email || undefined },
          update: { name: user.name || undefined, email: user.email || undefined },
        });
        imported++;
      } catch {
        skipped++;
      }
    }

    const total = await prisma.smsContactListEntry.count({ where: { listId } });
    await prisma.smsContactList.update({ where: { id: listId }, data: { totalContacts: total } });

    return { imported, skipped, total };
  },

  /**
   * Preview addresses with phone numbers matching filters
   */
  async previewAddresses(filters: { province?: string; supportLevel?: string }) {
    const where: Prisma.AddressWhereInput = { phone: { not: null } };
    if (filters.supportLevel) where.supportLevel = filters.supportLevel as SupportLevel;
    if (filters.province) where.location = { province: filters.province };

    const [total, sample] = await Promise.all([
      prisma.address.count({ where }),
      prisma.address.findMany({
        where,
        select: { phone: true, firstName: true, lastName: true },
        take: 10,
      }),
    ]);

    return {
      total,
      sample: sample.map((a) => ({
        phone: a.phone!,
        name: [a.firstName, a.lastName].filter(Boolean).join(' ') || undefined,
      })),
    };
  },

  /**
   * Import addresses with phone numbers into a contact list
   */
  async importFromAddresses(listId: string, filters: { province?: string; supportLevel?: string }) {
    const where: Prisma.AddressWhereInput = { phone: { not: null } };
    if (filters.supportLevel) where.supportLevel = filters.supportLevel as SupportLevel;
    if (filters.province) where.location = { province: filters.province };

    const addresses = await prisma.address.findMany({
      where,
      select: { phone: true, firstName: true, lastName: true, email: true },
    });

    let imported = 0;
    let skipped = 0;

    for (const addr of addresses) {
      const phone = normalizePhone(addr.phone!);
      if (!phone) { skipped++; continue; }

      const name = [addr.firstName, addr.lastName].filter(Boolean).join(' ') || undefined;
      try {
        await prisma.smsContactListEntry.upsert({
          where: { listId_phone: { listId, phone } },
          create: { listId, phone, name, email: addr.email || undefined },
          update: { name, email: addr.email || undefined },
        });
        imported++;
      } catch {
        skipped++;
      }
    }

    const total = await prisma.smsContactListEntry.count({ where: { listId } });
    await prisma.smsContactList.update({ where: { id: listId }, data: { totalContacts: total } });

    return { imported, skipped, total };
  },

  /**
   * Preview shift signups with phone numbers matching filters
   */
  async previewSignups(filters: { shiftId?: string }) {
    const where: Prisma.ShiftSignupWhereInput = { userPhone: { not: null } };
    if (filters.shiftId) where.shiftId = filters.shiftId;

    const [total, sample] = await Promise.all([
      prisma.shiftSignup.count({ where }),
      prisma.shiftSignup.findMany({
        where,
        select: { userPhone: true, userName: true },
        take: 10,
      }),
    ]);

    return {
      total,
      sample: sample.map((s) => ({ phone: s.userPhone!, name: s.userName || undefined })),
    };
  },

  /**
   * Import shift signups with phone numbers into a contact list
   */
  async importFromSignups(listId: string, filters: { shiftId?: string }) {
    const where: Prisma.ShiftSignupWhereInput = { userPhone: { not: null } };
    if (filters.shiftId) where.shiftId = filters.shiftId;

    const signups = await prisma.shiftSignup.findMany({
      where,
      select: { userPhone: true, userName: true, userEmail: true },
    });

    let imported = 0;
    let skipped = 0;

    for (const signup of signups) {
      const phone = normalizePhone(signup.userPhone!);
      if (!phone) { skipped++; continue; }

      try {
        await prisma.smsContactListEntry.upsert({
          where: { listId_phone: { listId, phone } },
          create: { listId, phone, name: signup.userName || undefined, email: signup.userEmail || undefined },
          update: { name: signup.userName || undefined, email: signup.userEmail || undefined },
        });
        imported++;
      } catch {
        skipped++;
      }
    }

    const total = await prisma.smsContactListEntry.count({ where: { listId } });
    await prisma.smsContactList.update({ where: { id: listId }, data: { totalContacts: total } });

    return { imported, skipped, total };
  },

  /**
   * Preview conversations matching filters (excluding opted-out)
   */
  async previewConversations(filters: { campaignId?: string; responseType?: string }) {
    const where: Prisma.SmsConversationWhereInput = { status: { not: 'OPTED_OUT' } };
    if (filters.campaignId) where.campaignId = filters.campaignId;
    if (filters.responseType) {
      where.messages = { some: { responseType: filters.responseType as SmsResponseType } };
    }

    const [total, sample] = await Promise.all([
      prisma.smsConversation.count({ where }),
      prisma.smsConversation.findMany({
        where,
        select: { phone: true, contactName: true },
        take: 10,
      }),
    ]);

    return {
      total,
      sample: sample.map((c) => ({ phone: c.phone, name: c.contactName || undefined })),
    };
  },

  /**
   * Import conversation contacts into a contact list (excluding opted-out)
   */
  async importFromConversations(listId: string, filters: { campaignId?: string; responseType?: string }) {
    const where: Prisma.SmsConversationWhereInput = { status: { not: 'OPTED_OUT' } };
    if (filters.campaignId) where.campaignId = filters.campaignId;
    if (filters.responseType) {
      where.messages = { some: { responseType: filters.responseType as SmsResponseType } };
    }

    const conversations = await prisma.smsConversation.findMany({
      where,
      select: { phone: true, contactName: true },
    });

    let imported = 0;
    let skipped = 0;

    for (const conv of conversations) {
      const phone = normalizePhone(conv.phone);
      if (!phone) { skipped++; continue; }

      try {
        await prisma.smsContactListEntry.upsert({
          where: { listId_phone: { listId, phone } },
          create: { listId, phone, name: conv.contactName || undefined },
          update: { name: conv.contactName || undefined },
        });
        imported++;
      } catch {
        skipped++;
      }
    }

    const total = await prisma.smsContactListEntry.count({ where: { listId } });
    await prisma.smsContactList.update({ where: { id: listId }, data: { totalContacts: total } });

    return { imported, skipped, total };
  },

  /**
   * Preview CRM contacts with phones matching filters
   */
  async previewContacts(filters: { tag?: string; supportLevel?: string }) {
    const where: Prisma.ContactWhereInput = {
      phones: { some: {} },
      mergedIntoId: null,
      doNotContact: false,
      smsOptOut: false,
    };
    if (filters.supportLevel) where.supportLevel = filters.supportLevel as SupportLevel;
    if (filters.tag) where.tags = { array_contains: [filters.tag] };

    const [total, sample] = await Promise.all([
      prisma.contact.count({ where }),
      prisma.contact.findMany({
        where,
        select: { displayName: true, phones: { select: { phone: true }, take: 1 } },
        take: 10,
      }),
    ]);

    return {
      total,
      sample: sample.map((c) => ({
        phone: c.phones[0]?.phone || '',
        name: c.displayName || undefined,
      })),
    };
  },

  /**
   * Import CRM contacts with phones into a contact list
   */
  async importFromContacts(listId: string, filters: { tag?: string; supportLevel?: string }) {
    const where: Prisma.ContactWhereInput = {
      phones: { some: {} },
      mergedIntoId: null,
      doNotContact: false,
      smsOptOut: false,
    };
    if (filters.supportLevel) where.supportLevel = filters.supportLevel as SupportLevel;
    if (filters.tag) where.tags = { array_contains: [filters.tag] };

    const contacts = await prisma.contact.findMany({
      where,
      select: { displayName: true, email: true, phones: { select: { phone: true } } },
    });

    let imported = 0;
    let skipped = 0;

    for (const contact of contacts) {
      for (const cp of contact.phones) {
        const phone = normalizePhone(cp.phone);
        if (!phone) { skipped++; continue; }

        try {
          await prisma.smsContactListEntry.upsert({
            where: { listId_phone: { listId, phone } },
            create: { listId, phone, name: contact.displayName || undefined, email: contact.email || undefined },
            update: { name: contact.displayName || undefined, email: contact.email || undefined },
          });
          imported++;
        } catch {
          skipped++;
        }
      }
    }

    const total = await prisma.smsContactListEntry.count({ where: { listId } });
    await prisma.smsContactList.update({ where: { id: listId }, data: { totalContacts: total } });

    return { imported, skipped, total };
  },
};
