import { Injectable, NotFoundException } from '@nestjs/common';
import { AuthenticatedUser } from '../auth/auth.types';
import {
  normalizeOptionalEmail,
  normalizePhoneNumber,
  normalizeText,
} from '../common/normalize';
import { PrismaService } from '../prisma/prisma.service';
import { CreateContactDto } from '../dto/create-contact.dto';
import { UpdateContactDto } from '../dto/update-contact.dto';

type ContactsQuery = {
  page: number;
  limit: number;
  search?: string;
  status?: string;
  tag?: string;
};

@Injectable()
export class ContactsService {
  constructor(private readonly prisma: PrismaService) {}

  async findAll(query: ContactsQuery) {
    const contacts = await this.prisma.contact.findMany({ orderBy: { createdAt: 'desc' } });
    const enriched = await Promise.all(contacts.map((contact) => this.enrichContactRow(contact)));
    const filtered = enriched.filter((contact) => {
      const matchesSearch = !query.search
        || [contact.name, contact.email, contact.phoneNumber, contact.company, contact.location]
          .filter(Boolean)
          .some((value) => String(value).toLowerCase().includes(query.search!.trim().toLowerCase()));
      const matchesStatus = !query.status
        || query.status === 'all'
        || contact.status.toLowerCase() === query.status.trim().toLowerCase();
      const matchesTag = !query.tag
        || query.tag === 'all'
        || contact.tags.some((tag) => tag.toLowerCase() === query.tag!.trim().toLowerCase());

      return matchesSearch && matchesStatus && matchesTag;
    });

    const pageSize = Math.min(Math.max(query.limit || 10, 1), 100);
    const page = Math.max(query.page || 1, 1);
    const total = filtered.length;
    const totalPages = Math.max(1, Math.ceil(total / pageSize));
    const start = (page - 1) * pageSize;
    const items = filtered.slice(start, start + pageSize);
    const availableTags = Array.from(new Set(enriched.flatMap((contact) => contact.tags))).sort();

    return {
      items,
      total,
      page,
      pageSize,
      totalPages,
      availableTags,
      statusCounts: {
        active: enriched.filter((item) => item.status === 'Active').length,
        inactive: enriched.filter((item) => item.status === 'Inactive').length,
      },
    };
  }

  async findOne(id: string) {
    const contact = await this.prisma.contact.findUnique({ where: { id } });
    if (!contact) {
      throw new NotFoundException('Contact not found');
    }

    const [row, webhookEvents, auditLogs] = await Promise.all([
      this.enrichContactRow(contact),
      this.prisma.webhookEvent.findMany({
        where: {
          OR: [
            { senderPhone: contact.phoneNumber },
            { recipientPhone: contact.phoneNumber },
          ],
        },
        orderBy: { createdAt: 'desc' },
        take: 20,
      }),
      this.prisma.auditLog.findMany({
        where: {
          module: 'Contacts',
          OR: [
            { details: { contains: contact.phoneNumber, mode: 'insensitive' } },
            { details: { contains: contact.name, mode: 'insensitive' } },
          ],
        },
        orderBy: { createdAt: 'desc' },
        take: 20,
      }),
    ]);

    const notes = this.buildNotes(contact);
    const history = [
      ...webhookEvents.map((event) => ({
        id: event.id,
        type: event.senderPhone === contact.phoneNumber ? 'inbound' : 'message',
        title: event.senderPhone === contact.phoneNumber ? 'Inbound Message' : 'Message Sent',
        at: event.createdAt.toISOString(),
        summary:
          event.senderPhone === contact.phoneNumber
            ? `Inbound WhatsApp event ${event.eventType} received from this contact.`
            : `Outbound WhatsApp event ${event.eventType} delivered to this contact.`,
        status: event.processingStatus,
        errorReason: event.processingNotes,
      })),
      ...auditLogs.map((log) => ({
        id: log.id,
        type: log.actionType.toLowerCase().includes('deleted')
          ? 'status'
          : log.actionType.toLowerCase().includes('created')
            ? 'tag'
            : 'system',
        title: log.actionType,
        at: log.createdAt.toISOString(),
        summary: log.details,
        status: log.severity,
        errorReason: null as string | null,
      })),
    ]
      .sort((left, right) => new Date(right.at).getTime() - new Date(left.at).getTime())
      .slice(0, 20);

    return {
      contact: {
        ...row,
        notes,
        history,
      },
    };
  }

  async exportContacts(query: ContactsQuery, user: AuthenticatedUser, ipAddress?: string) {
    const actor = await this.prisma.user.findUnique({
      where: { id: user.sub },
      select: { id: true, name: true, email: true },
    });
    const result = await this.findAll(query);
    const lines = [
      ['Name', 'Email', 'Phone Number', 'Company', 'Location', 'Status', 'Tags', 'Last Message'].join(','),
      ...result.items.map((contact) =>
        [
          contact.name,
          contact.email || '',
          contact.phoneNumber,
          contact.company || '',
          contact.location,
          contact.status,
          contact.tags.join(' | '),
          contact.lastMessageLabel,
        ]
          .map((cell) => `"${String(cell).replaceAll('"', '""')}"`)
          .join(','),
      ),
    ].join('\n');

    await this.prisma.auditLog.create({
      data: {
        actorUserId: actor?.id || user.sub,
        actorName: actor?.name || user.email,
        actorEmail: actor?.email || user.email,
        actionType: 'Contact Exported',
        module: 'Contacts',
        ipAddress: ipAddress || null,
        severity: 'default',
        details: `Exported ${result.total} contact rows.`,
      },
    });

    return {
      fileName: 'contacts-directory.csv',
      contentType: 'text/csv; charset=utf-8',
      buffer: Buffer.from(lines, 'utf8'),
    };
  }

  async create(dto: CreateContactDto, user: AuthenticatedUser, ipAddress?: string) {
    const actor = await this.prisma.user.findUnique({
      where: { id: user.sub },
      select: { id: true, name: true, email: true },
    });
    const contact = await this.prisma.contact.create({
      data: {
        name: normalizeText(dto.name)!,
        phoneNumber: normalizePhoneNumber(dto.phoneNumber),
        email: normalizeOptionalEmail(dto.email),
        company: normalizeText(dto.company),
        notes: normalizeText(dto.notes),
      },
    });

    await this.prisma.auditLog.create({
      data: {
        actorUserId: actor?.id || user.sub,
        actorName: actor?.name || user.email,
        actorEmail: actor?.email || user.email,
        actionType: 'Contact Created',
        module: 'Contacts',
        ipAddress: ipAddress || null,
        severity: 'default',
        details: `Created contact ${contact.name} (${contact.phoneNumber}).`,
      },
    });

    return contact;
  }

  async update(id: string, dto: UpdateContactDto, user: AuthenticatedUser, ipAddress?: string) {
    const actor = await this.prisma.user.findUnique({
      where: { id: user.sub },
      select: { id: true, name: true, email: true },
    });
    const contact = await this.prisma.contact.update({
      where: { id },
      data: {
        name: dto.name === undefined ? undefined : normalizeText(dto.name),
        phoneNumber:
          dto.phoneNumber === undefined ? undefined : normalizePhoneNumber(dto.phoneNumber),
        email: dto.email === undefined ? undefined : normalizeOptionalEmail(dto.email),
        company: dto.company === undefined ? undefined : normalizeText(dto.company),
        notes: dto.notes === undefined ? undefined : normalizeText(dto.notes),
        isBlacklisted: dto.isBlacklisted,
      },
    });

    await this.prisma.auditLog.create({
      data: {
        actorUserId: actor?.id || user.sub,
        actorName: actor?.name || user.email,
        actorEmail: actor?.email || user.email,
        actionType: 'Contact Updated',
        module: 'Contacts',
        ipAddress: ipAddress || null,
        severity: 'default',
        details: `Updated contact ${contact.name} (${contact.phoneNumber}).`,
      },
    });

    return contact;
  }

  async remove(id: string, user: AuthenticatedUser, ipAddress?: string) {
    const actor = await this.prisma.user.findUnique({
      where: { id: user.sub },
      select: { id: true, name: true, email: true },
    });
    const contact = await this.prisma.contact.delete({ where: { id } });

    await this.prisma.auditLog.create({
      data: {
        actorUserId: actor?.id || user.sub,
        actorName: actor?.name || user.email,
        actorEmail: actor?.email || user.email,
        actionType: 'Contact Deleted',
        module: 'Contacts',
        ipAddress: ipAddress || null,
        severity: 'default',
        details: `Deleted contact ${contact.name} (${contact.phoneNumber}).`,
      },
    });

    return contact;
  }

  private async enrichContactRow(contact: Awaited<ReturnType<typeof this.prisma.contact.findFirstOrThrow>>) {
    const latestWebhook = await this.prisma.webhookEvent.findFirst({
      where: {
        OR: [
          { senderPhone: contact.phoneNumber },
          { recipientPhone: contact.phoneNumber },
        ],
      },
      orderBy: { createdAt: 'desc' },
    });

    const tags = this.deriveTags(contact);
    return {
      id: contact.id,
      name: contact.name,
      email: contact.email,
      phoneNumber: contact.phoneNumber,
      company: contact.company,
      status: this.deriveStatus(contact),
      tags,
      location: this.deriveLocation(contact.phoneNumber, contact.email),
      lastMessageAt: latestWebhook?.createdAt.toISOString() || null,
      lastMessageLabel: latestWebhook
        ? latestWebhook.createdAt.toLocaleDateString('en-US', {
            month: 'short',
            day: 'numeric',
            year: 'numeric',
            timeZone: 'UTC',
          })
        : contact.updatedAt.toLocaleDateString('en-US', {
            month: 'short',
            day: 'numeric',
            year: 'numeric',
            timeZone: 'UTC',
          }),
      lastSeenLabel: this.relativeTime(latestWebhook?.createdAt || contact.updatedAt),
      isBlacklisted: contact.isBlacklisted,
      avatarInitials: this.getInitials(contact.name),
      createdAt: contact.createdAt.toISOString(),
      updatedAt: contact.updatedAt.toISOString(),
    };
  }

  private deriveStatus(contact: {
    isBlacklisted: boolean;
    updatedAt: Date;
  }) {
    if (contact.isBlacklisted) {
      return 'Inactive';
    }

    const inactiveThreshold = Date.now() - 1000 * 60 * 60 * 24 * 45;
    return contact.updatedAt.getTime() >= inactiveThreshold ? 'Active' : 'Inactive';
  }

  private deriveTags(contact: {
    company: string | null;
    notes: string | null;
    createdAt: Date;
    email: string | null;
  }) {
    const haystack = `${contact.company || ''} ${contact.notes || ''} ${contact.email || ''}`.toLowerCase();
    const tags = new Set<string>();

    if (haystack.includes('vip') || haystack.includes('premium')) tags.add('VIP');
    if (haystack.includes('retail') || haystack.includes('shop') || haystack.includes('store')) tags.add('Retail');
    if (haystack.includes('support') || haystack.includes('help') || haystack.includes('issue')) tags.add('Support');
    if (haystack.includes('lead') || haystack.includes('prospect')) tags.add('Lead');
    if (haystack.includes('wholesale') || haystack.includes('logistics') || haystack.includes('distribution')) tags.add('Wholesale');
    if (contact.createdAt.getTime() >= Date.now() - 1000 * 60 * 60 * 24 * 30) tags.add('New');
    if (contact.company?.toLowerCase().includes('bizone')) tags.add('Partner');

    if (tags.size === 0) {
      tags.add('General');
    }

    return Array.from(tags);
  }

  private deriveLocation(phoneNumber: string, email?: string | null) {
    const value = phoneNumber.replace(/\s+/g, '');
    if (value.startsWith('+62') || value.startsWith('62')) return 'Jakarta, ID';
    if (value.startsWith('+65') || value.startsWith('65')) return 'Singapore, SG';
    if (value.startsWith('+1') || value.startsWith('1')) return 'San Francisco, US';
    if (email?.endsWith('.es')) return 'Madrid, ES';
    if (email?.endsWith('.net')) return 'London, UK';
    return 'Regional Contact';
  }

  private buildNotes(contact: { notes: string | null; company: string | null; createdAt: Date }) {
    const items = (contact.notes || '')
      .split(/\n+/)
      .map((note) => note.trim())
      .filter(Boolean);

    if (items.length === 0) {
      return [
        {
          id: 'default-1',
          author: 'System Admin',
          dateLabel: contact.createdAt.toLocaleDateString('en-US', {
            month: 'short',
            day: 'numeric',
            year: 'numeric',
            timeZone: 'UTC',
          }),
          body: contact.company
            ? `Associated with ${contact.company}. Keep communication contextual to this account.`
            : 'No private notes yet. Add a note to capture relationship context and preferences.',
          emphasized: true,
        },
      ];
    }

    return items.map((item, index) => ({
      id: `note-${index + 1}`,
      author: index === 0 ? 'System Admin' : 'Ops Team',
      dateLabel: contact.createdAt.toLocaleDateString('en-US', {
        month: 'short',
        day: 'numeric',
        year: 'numeric',
        timeZone: 'UTC',
      }),
      body: item,
      emphasized: index === 0,
    }));
  }

  private relativeTime(date: Date) {
    const diffMs = Date.now() - date.getTime();
    const diffMinutes = Math.max(1, Math.round(diffMs / 60000));
    if (diffMinutes < 60) return `${diffMinutes} mins ago`;
    const diffHours = Math.round(diffMinutes / 60);
    if (diffHours < 24) return `${diffHours} hours ago`;
    const diffDays = Math.round(diffHours / 24);
    return `${diffDays} days ago`;
  }

  private getInitials(name: string) {
    return name
      .split(/\s+/)
      .filter(Boolean)
      .slice(0, 2)
      .map((part) => part[0]?.toUpperCase() || '')
      .join('');
  }
}
