import { createHmac, createHash, timingSafeEqual } from 'node:crypto';
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
import type {
  NormalizedWebhookEvent,
} from './webhooks.types';

function asRecord(value: unknown): Record<string, unknown> | null {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    return null;
  }

  return value as Record<string, unknown>;
}

function asArray(value: unknown) {
  return Array.isArray(value) ? value : [];
}

function readString(value: unknown) {
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
}

function readTimestamp(value: unknown) {
  if (typeof value === 'number' || (typeof value === 'string' && value.trim())) {
    const raw = String(value).trim();
    const milliseconds = /^\d+$/.test(raw) ? Number(raw) * 1000 : Date.parse(raw);
    if (!Number.isNaN(milliseconds)) {
      return new Date(milliseconds);
    }
  }

  return new Date();
}

function buildEventId(provider: string, payload: Record<string, unknown>, seed?: string) {
  const hash = createHash('sha256')
    .update(provider)
    .update(':')
    .update(seed || JSON.stringify(payload))
    .digest('hex');

  return `evt_${hash.slice(0, 32)}`;
}

function buildMetaEvents(payload: Record<string, unknown>, provider: string) {
  const normalized: NormalizedWebhookEvent[] = [];
  const entries = asArray(payload.entry);

  for (const entry of entries) {
    const entryRecord = asRecord(entry);
    const changes = asArray(entryRecord?.changes);

    for (const change of changes) {
      const changeRecord = asRecord(change);
      const field = readString(changeRecord?.field);
      const value = asRecord(changeRecord?.value);

      if (!value) {
        continue;
      }

      const metadata = asRecord(value.metadata);
      const recipientPhone = readString(metadata?.display_phone_number) || readString(metadata?.phone_number_id);

      for (const status of asArray(value.statuses)) {
        const statusRecord = asRecord(status);
        if (!statusRecord) {
          continue;
        }

        const rawStatus = readString(statusRecord.status) || 'sent';
        const statusMap: Record<string, string> = {
          sent: 'message.sent',
          delivered: 'message.delivered',
          read: 'message.read',
          failed: 'message.failed',
        };

        normalized.push({
          provider,
          eventType: statusMap[rawStatus] || 'message.sent',
          eventId:
            readString(statusRecord.id) ||
            readString(statusRecord.meta_msg_id) ||
            buildEventId(provider, statusRecord, rawStatus),
          senderPhone: readString(statusRecord.recipient_id),
          recipientPhone,
          externalMessageId: readString(statusRecord.id),
          eventTimestamp: readTimestamp(statusRecord.timestamp),
          payload: statusRecord,
        });
      }

      for (const message of asArray(value.messages)) {
        const messageRecord = asRecord(message);
        if (!messageRecord) {
          continue;
        }

        const contacts = asArray(value.contacts);
        const firstContact = asRecord(contacts[0]);
        const senderPhone =
          readString(messageRecord.from) ||
          readString(firstContact?.wa_id) ||
          readString(firstContact?.phone);

        normalized.push({
          provider,
          eventType: field === 'messages' ? 'message.inbound' : 'account.updated',
          eventId:
            readString(messageRecord.id) ||
            buildEventId(provider, messageRecord, readString(messageRecord.from)),
          senderPhone,
          recipientPhone,
          externalMessageId: readString(messageRecord.id),
          eventTimestamp: readTimestamp(messageRecord.timestamp),
          payload: messageRecord,
        });
      }

      if (field === 'message_template_status_update') {
        normalized.push({
          provider,
          eventType: 'template.updated',
          eventId: buildEventId(provider, value, 'template'),
          recipientPhone,
          eventTimestamp: new Date(),
          payload: value,
        });
      }
    }
  }

  return normalized;
}

function buildGenericEvents(payload: Record<string, unknown>, provider: string) {
  const eventType =
    readString(payload.event_type) ||
    readString(payload.eventType) ||
    readString(payload.type) ||
    'account.updated';
  const senderPhone =
    readString(payload.sender_phone) ||
    readString(payload.senderPhone) ||
    readString(payload.from);
  const recipientPhone =
    readString(payload.recipient_phone) ||
    readString(payload.recipientPhone) ||
    readString(payload.to);
  const externalMessageId =
    readString(payload.external_message_id) ||
    readString(payload.externalMessageId) ||
    readString(payload.message_id) ||
    readString(payload.messageId);
  const seed =
    readString(payload.event_id) ||
    readString(payload.id) ||
    externalMessageId ||
    JSON.stringify(payload);

  return [
    {
      provider,
      eventType,
      eventId:
        readString(payload.event_id) ||
        readString(payload.eventId) ||
        readString(payload.id) ||
        buildEventId(provider, payload, seed),
      senderPhone,
      recipientPhone,
      externalMessageId,
      eventTimestamp: readTimestamp(payload.timestamp || payload.created_at || payload.createdAt),
      payload,
    },
  ];
}

export function verifyMetaSignature(rawBody: Buffer, signatureHeader: string, appSecret: string) {
  const [scheme, receivedSignature] = signatureHeader.split('=');
  if (scheme !== 'sha256' || !receivedSignature) {
    throw new UnauthorizedException('Invalid meta webhook signature format');
  }

  const expectedSignature = createHmac('sha256', appSecret).update(rawBody).digest('hex');
  const receivedBuffer = Buffer.from(receivedSignature, 'hex');
  const expectedBuffer = Buffer.from(expectedSignature, 'hex');

  if (
    receivedBuffer.length !== expectedBuffer.length ||
    !timingSafeEqual(receivedBuffer, expectedBuffer)
  ) {
    throw new UnauthorizedException('Invalid meta webhook signature');
  }
}

export function normalizeWebhookPayload(provider: string, payload: unknown) {
  const payloadRecord = asRecord(payload);
  if (!payloadRecord) {
    throw new BadRequestException('Webhook payload must be a JSON object');
  }

  const normalizedProvider = provider.toLowerCase();
  if (
    normalizedProvider === 'meta' &&
    readString(payloadRecord.object) === 'whatsapp_business_account'
  ) {
    const metaEvents = buildMetaEvents(payloadRecord, normalizedProvider);
    if (metaEvents.length > 0) {
      return metaEvents;
    }
  }

  return buildGenericEvents(payloadRecord, normalizedProvider);
}
