import type {
  Channel,
  NormalizedMessage,
  ResolvedCredentials,
  ProviderErrorCode,
} from '@zyphon/shared-types';
import type {
  ProviderAdapter,
  ProviderResult,
  ProviderHealth,
  WebhookRequest,
  DeliveryEvent,
} from '@zyphon/provider-contracts';

function whatsappMediaCategory(
  contentType = '',
  filename = '',
): 'image' | 'document' | 'video' | 'audio' {
  const ct = contentType.toLowerCase();
  const fn = filename.toLowerCase();
  if (ct.startsWith('image/') || /\.(png|jpe?g|webp|gif)$/.test(fn)) return 'image';
  if (ct.startsWith('video/') || /\.(mp4|3gp|mov)$/.test(fn)) return 'video';
  if (ct.startsWith('audio/') || /\.(mp3|ogg|aac|m4a|amr)$/.test(fn)) return 'audio';
  return 'document';
}

export class MetaWhatsAppProviderAdapter implements ProviderAdapter {
  public readonly id: string;
  public readonly channel: Channel = 'whatsapp';
  private fetchFn: typeof fetch;

  constructor(options: { id?: string; fetchFn?: typeof fetch } = {}) {
    this.id = options.id || 'meta-whatsapp';
    this.fetchFn = options.fetchFn ?? globalThis.fetch.bind(globalThis);
  }

  public async validateConfiguration(c: ResolvedCredentials): Promise<void> {
    const phoneId = c.phone_id || c.phone_number_id || c.phoneNumberId;
    const token = c.token || c.access_token || c.accessToken;
    if (!phoneId || !token) {
      throw new Error("Meta WhatsApp configuration requires 'phone_id' and 'token'");
    }
  }

  public async send(
    m: NormalizedMessage,
    c: ResolvedCredentials,
    signal: AbortSignal
  ): Promise<ProviderResult> {
    const phoneId = String(c.phone_id || c.phone_number_id || c.phoneNumberId || '');
    const token = String(c.token || c.access_token || c.accessToken || '');

    const url = `https://graph.facebook.com/v19.0/${encodeURIComponent(phoneId)}/messages`;

    // Strip leading '+' for Meta API phone recipient format
    const recipientNumber = m.recipient.replace(/^\+/, '');

    const templateName = m.content.template ? String(m.content.template) : '';
    const varValues =
      (m.metadata?.template_variable_values as string[] | undefined) ||
      Object.values(
        (m.metadata?.template_variables as Record<string, string> | undefined) ||
          m.content.variables ||
          {},
      ).map((v) => String(v));

    const firstAtt = m.content.attachments?.[0];
    let bodyPayload: Record<string, unknown>;

    if (templateName) {
      bodyPayload = {
        messaging_product: 'whatsapp',
        recipient_type: 'individual',
        to: recipientNumber,
        type: 'template',
        template: {
          name: templateName,
          language: { code: String(m.metadata?.language_code || c.language || 'en') },
          ...(varValues.length > 0
            ? {
                components: [
                  {
                    type: 'body',
                    parameters: varValues.map((text) => ({ type: 'text', text })),
                  },
                ],
              }
            : {}),
        },
      };
    } else if (firstAtt && (firstAtt.url || firstAtt.content)) {
      const mediaKind = whatsappMediaCategory(firstAtt.contentType, firstAtt.filename);
      let mediaRef: Record<string, unknown>;

      if (firstAtt.url) {
        mediaRef = { link: firstAtt.url };
      } else {
        // Upload base64 bytes to Meta /{phone-id}/media to get an id
        const mediaUploadUrl = `https://graph.facebook.com/v19.0/${encodeURIComponent(phoneId)}/media`;
        const form = new FormData();
        form.set('messaging_product', 'whatsapp');
        form.set('type', firstAtt.contentType || 'application/octet-stream');
        const bytes = new Uint8Array(Buffer.from(String(firstAtt.content || ''), 'base64'));
        form.append(
          'file',
          new Blob([bytes], { type: firstAtt.contentType || 'application/octet-stream' }),
          firstAtt.filename || 'attachment',
        );

        const upRes = await this.fetchFn(mediaUploadUrl, {
          method: 'POST',
          headers: { Authorization: `Bearer ${token}` },
          body: form,
          signal,
        });
        const upData: any = await upRes.json().catch(() => ({}));
        if (!upRes.ok || !upData.id) {
          return {
            success: false,
            error: {
              code: upRes.status === 401 || upRes.status === 403 ? 'CREDENTIAL_ERROR' : 'PROVIDER_REJECTED',
              message: upData.error?.message || `Meta WhatsApp media upload failed (${upRes.status})`,
              raw: upData,
            },
          };
        }
        mediaRef = { id: String(upData.id) };
      }

      if (m.content.body && mediaKind !== 'audio') {
        mediaRef.caption = m.content.body;
      }
      if (mediaKind === 'document' && firstAtt.filename) {
        mediaRef.filename = firstAtt.filename;
      }

      bodyPayload = {
        messaging_product: 'whatsapp',
        recipient_type: 'individual',
        to: recipientNumber,
        type: mediaKind,
        [mediaKind]: mediaRef,
      };
    } else {
      bodyPayload = {
        messaging_product: 'whatsapp',
        recipient_type: 'individual',
        to: recipientNumber,
        type: 'text',
        text: {
          preview_url: false,
          body: m.content.body,
        },
      };
    }

    try {
      const res = await this.fetchFn(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${token}`,
        },
        body: JSON.stringify(bodyPayload),
        signal,
      });

      const resData: any = await res.json().catch(() => ({}));

      if (res.status === 200 || res.status === 201) {
        const messageId = resData.messages?.[0]?.id || `wamid_${Date.now()}`;
        return {
          success: true,
          provider_reference: messageId,
          raw_response: resData,
        };
      }

      let code: ProviderErrorCode = 'PROVIDER_5XX';
      if (res.status === 401 || res.status === 403) code = 'CREDENTIAL_ERROR';
      else if (res.status === 400) code = 'PROVIDER_REJECTED';
      else if (res.status === 429) code = 'PROVIDER_UNAVAILABLE';

      return {
        success: false,
        error: {
          code,
          message: resData.error?.message || `Meta WhatsApp error status: ${res.status}`,
          raw: resData,
        },
      };
    } catch (err: any) {
      const isTimeout = signal.aborted || err.name === 'AbortError';
      return {
        success: false,
        error: {
          code: isTimeout ? 'TIMEOUT' : 'PROVIDER_5XX',
          message: err.message || 'Meta WhatsApp dispatch failed',
        },
      };
    }
  }

  public async healthCheck(c: ResolvedCredentials): Promise<ProviderHealth> {
    const startTime = Date.now();
    const phoneId = String(c.phone_id || c.phone_number_id || c.phoneNumberId || '');
    const token = String(c.token || c.access_token || c.accessToken || '');

    if (!phoneId || !token) {
      return { status: 'down', latency_ms: 0, checked_at: Date.now(), message: 'Missing credentials' };
    }

    const url = `https://graph.facebook.com/v19.0/${encodeURIComponent(phoneId)}`;

    try {
      const res = await this.fetchFn(url, {
        method: 'GET',
        headers: { Authorization: `Bearer ${token}` },
      });

      const latency = Date.now() - startTime;
      if (res.ok) {
        return { status: 'healthy', latency_ms: latency, checked_at: Date.now() };
      }
      return { status: 'down', latency_ms: latency, checked_at: Date.now(), message: `HTTP ${res.status}` };
    } catch (err: any) {
      return { status: 'down', latency_ms: Date.now() - startTime, checked_at: Date.now(), message: err.message };
    }
  }

  public parseWebhook(req: WebhookRequest): DeliveryEvent | null {
    const body = (req.body as any) || {};
    try {
      const entry = body.entry?.[0];
      const change = entry?.changes?.[0];
      const statusObj = change?.value?.statuses?.[0];

      if (statusObj?.id && statusObj?.status) {
        const isDelivered = statusObj.status === 'delivered' || statusObj.status === 'read';
        return {
          message_id: statusObj.id,
          status: isDelivered ? 'delivered' : 'undelivered',
          timestamp: Number(statusObj.timestamp) * 1000 || Date.now(),
          provider: this.id,
          raw: statusObj,
        };
      }
    } catch {}

    return null;
  }
}

