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

const GUPSHUP_SMS_SCHEMA: FieldSchema[] = [
  { key: 'userid', label: 'User ID', type: 'text', required: true, section: 'Account' },
  { key: 'password', label: 'Password', type: 'secret', required: true, section: 'Account' },
  { key: 'sender_id', label: 'Sender ID (header)', type: 'text', required: true, validator: 'header', dlt: true, section: 'India DLT' },
  { key: 'pe_id', label: 'Principal Entity ID', type: 'text', required: true, validator: 'digits19', dlt: true, section: 'India DLT' },
  { key: 'template_id', label: 'Default DLT template ID', type: 'text', optional: true, dlt: true, section: 'India DLT' },
];

export class GupshupSmsProviderAdapter implements ProviderAdapter {
  public readonly id: string;
  public readonly channel: Channel = 'sms';
  public readonly fieldSchema = GUPSHUP_SMS_SCHEMA;
  private fetchFn: typeof fetch;

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

  public async validateConfiguration(c: ResolvedCredentials): Promise<void> {
    if (!c.userid) throw new Error("Gupshup Enterprise SMS requires 'userid'");
    if (!c.password) throw new Error("Gupshup Enterprise SMS requires 'password'");
    if (!c.sender_id) throw new Error("Gupshup Enterprise SMS requires DLT 'sender_id'");
    if (!c.pe_id) throw new Error("Gupshup Enterprise SMS requires DLT 'pe_id'");
  }

  public async send(
    m: NormalizedMessage,
    c: ResolvedCredentials,
    signal: AbortSignal,
  ): Promise<ProviderResult> {
    try {
      await this.validateConfiguration(c);
    } catch (err: any) {
      return { success: false, error: { code: 'CREDENTIAL_ERROR', message: err.message } };
    }

    const contentTemplate = m.content.template ? String(m.content.template) : '';
    const looksLikeDltId = /^\d{10,}$/.test(contentTemplate);
    const templateId =
      (m.metadata?.dlt_template_id as string | undefined) ||
      (looksLikeDltId ? contentTemplate : '') ||
      (c.template_id ? String(c.template_id) : '');

    if (!templateId) {
      return {
        success: false,
        error: {
          code: 'PROVIDER_REJECTED',
          message: 'Gupshup Enterprise SMS requires a DLT template ID on the message or provider config',
        },
      };
    }

    const mobile = m.recipient.replace(/\D/g, '');
    const sendTo = mobile.length === 10 ? `91${mobile}` : mobile;

    const params = new URLSearchParams({
      method: 'SendMessage',
      send_to: sendTo,
      msg: m.content.body,
      msg_type: 'TEXT',
      userid: String(c.userid),
      auth_scheme: 'plain',
      password: String(c.password),
      v: '1.1',
      format: 'json',
      mask: String(c.sender_id),
      principalEntityId: String(c.pe_id),
      dltTemplateId: templateId,
    });

    try {
      const res = await this.fetchFn('https://enterprise.smsgupshup.com/GatewayAPI/rest', {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: params.toString(),
        signal,
      });

      const rawBody = await res.text().catch(() => '');
      let data: any = { raw_text: rawBody };
      try {
        if (rawBody.trim().startsWith('{')) {
          data = JSON.parse(rawBody);
        }
      } catch {
        data = { raw_text: rawBody };
      }

      const respStatus = data?.response?.status || '';
      const rawText = String(data?.raw_text || '').trim();
      const isOk =
        res.ok &&
        (String(respStatus).toLowerCase() === 'success' || rawText.toLowerCase().startsWith('success'));

      if (isOk) {
        const pipeParts = rawText.split('|').map((s) => s.trim());
        const pipeRef = pipeParts.length >= 3 ? pipeParts[2] : undefined;
        const ref = data?.response?.id || data?.response?.details || pipeRef || `gs_sms_${Date.now()}`;
        return {
          success: true,
          provider_reference: String(ref),
          raw_response: data,
        };
      }

      let code: ProviderErrorCode = 'PROVIDER_5XX';
      if (res.status === 401 || res.status === 403) code = 'CREDENTIAL_ERROR';
      else if (res.status >= 400 && res.status < 500) code = 'PROVIDER_REJECTED';
      else if (String(respStatus).toLowerCase() === 'error') code = 'PROVIDER_REJECTED';

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

  public async healthCheck(c: ResolvedCredentials): Promise<ProviderHealth> {
    const start = Date.now();
    try {
      await this.validateConfiguration(c);
      return { status: 'healthy', latency_ms: Date.now() - start, checked_at: Date.now() };
    } catch (err: any) {
      return { status: 'down', latency_ms: Date.now() - start, checked_at: Date.now(), message: err.message };
    }
  }

  public parseWebhook(req: WebhookRequest): DeliveryEvent | null {
    const body = (req.body || req.query || {}) as Record<string, unknown>;
    const msgId = body.externalId || body.id || body.message_id;
    const statusStr = String(body.status || body.cause || '').toUpperCase();
    if (!msgId || !statusStr) return null;
    const isDelivered = statusStr === 'DELIVERED' || statusStr === 'SUCCESS';
    return {
      message_id: String(msgId),
      status: isDelivered ? 'delivered' : 'undelivered',
      timestamp: Date.now(),
      provider: this.id,
      raw: body,
    };
  }
}

const KALEYRA_SMS_SCHEMA: FieldSchema[] = [
  { key: 'api_key', label: 'API key', type: 'secret', required: true, section: 'Account' },
  { key: 'sid', label: 'SID', type: 'text', required: true, section: 'Account' },
  { key: 'host', label: 'API host', type: 'text', default: 'api.kaleyra.io', validator: 'host', section: 'Account' },
  { key: 'sender_id', label: 'Sender ID (header)', type: 'text', required: true, validator: 'header', dlt: true, section: 'India DLT' },
  { key: 'pe_id', label: 'Principal Entity ID', type: 'text', optional: true, validator: 'digits19', dlt: true, section: 'India DLT' },
  { key: 'template_id', label: 'Default DLT template ID', type: 'text', optional: true, dlt: true, section: 'India DLT' },
];

export class KaleyraSmsProviderAdapter implements ProviderAdapter {
  public readonly id: string;
  public readonly channel: Channel = 'sms';
  public readonly fieldSchema = KALEYRA_SMS_SCHEMA;
  private fetchFn: typeof fetch;

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

  public async validateConfiguration(c: ResolvedCredentials): Promise<void> {
    if (!c.api_key) throw new Error("Kaleyra SMS requires 'api_key'");
    if (!c.sid) throw new Error("Kaleyra SMS requires 'sid'");
    if (!c.sender_id) throw new Error("Kaleyra SMS requires DLT 'sender_id'");
  }

  public async send(
    m: NormalizedMessage,
    c: ResolvedCredentials,
    signal: AbortSignal,
  ): Promise<ProviderResult> {
    try {
      await this.validateConfiguration(c);
    } catch (err: any) {
      return { success: false, error: { code: 'CREDENTIAL_ERROR', message: err.message } };
    }

    const host = String(c.host || 'api.kaleyra.io').replace(/^https?:\/\//, '');
    const sid = String(c.sid);
    const contentTemplate = m.content.template ? String(m.content.template) : '';
    const looksLikeDltId = /^\d{10,}$/.test(contentTemplate);
    const templateId =
      (m.metadata?.dlt_template_id as string | undefined) ||
      (looksLikeDltId ? contentTemplate : '') ||
      (c.template_id ? String(c.template_id) : '');

    if (!templateId) {
      return {
        success: false,
        error: {
          code: 'PROVIDER_REJECTED',
          message: 'Kaleyra India SMS requires a DLT template_id on the message or provider config',
        },
      };
    }

    const digits = m.recipient.replace(/\D/g, '');
    const toPhone = digits.length === 10 ? `+91${digits}` : `+${digits}`;

    const params = new URLSearchParams({
      to: toPhone,
      type: String(m.metadata?.dlt_category || '').toLowerCase() === 'otp' ? 'OTP' : 'TXN',
      sender: String(c.sender_id),
      body: m.content.body,
      template_id: templateId,
    });
    if (c.pe_id) {
      params.set('entity_id', String(c.pe_id));
    }

    const url = `https://${host}/v1/${encodeURIComponent(sid)}/messages`;

    try {
      const res = await this.fetchFn(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'api-key': String(c.api_key),
        },
        body: params.toString(),
        signal,
      });

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

      if (res.ok && (res.status === 200 || res.status === 201 || res.status === 202)) {
        const ref = data.id || data.data?.[0]?.message_id || `klr_${Date.now()}`;
        return {
          success: true,
          provider_reference: String(ref),
          raw_response: data,
        };
      }

      let code: ProviderErrorCode = 'PROVIDER_5XX';
      if (res.status === 401 || res.status === 403) code = 'CREDENTIAL_ERROR';
      else if (res.status >= 400 && res.status < 500) code = 'PROVIDER_REJECTED';

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

  public async healthCheck(c: ResolvedCredentials): Promise<ProviderHealth> {
    const start = Date.now();
    try {
      await this.validateConfiguration(c);
      return { status: 'healthy', latency_ms: Date.now() - start, checked_at: Date.now() };
    } catch (err: any) {
      return { status: 'down', latency_ms: Date.now() - start, checked_at: Date.now(), message: err.message };
    }
  }

  public parseWebhook(req: WebhookRequest): DeliveryEvent | null {
    const body = (req.body || req.query || {}) as Record<string, unknown>;
    const msgId = body.id || body.message_id;
    const statusStr = String(body.status || '').toLowerCase();
    if (!msgId || !statusStr) return null;
    return {
      message_id: String(msgId),
      status: statusStr === 'delivered' || statusStr === 'delivrd' ? 'delivered' : 'undelivered',
      timestamp: Date.now(),
      provider: this.id,
      raw: body,
    };
  }
}

const EXOTEL_SMS_SCHEMA: FieldSchema[] = [
  { key: 'sid', label: 'Account SID', type: 'text', required: true, section: 'Account' },
  { key: 'api_key', label: 'API key', type: 'text', required: true, section: 'Account' },
  { key: 'api_token', label: 'API token', type: 'secret', required: true, section: 'Account' },
  { key: 'sender_id', label: 'Sender ID (header)', type: 'text', required: true, validator: 'header', dlt: true, section: 'India DLT' },
  { key: 'pe_id', label: 'Principal Entity ID', type: 'text', required: true, validator: 'digits19', dlt: true, section: 'India DLT' },
  { key: 'template_id', label: 'Default DLT template ID', type: 'text', optional: true, dlt: true, section: 'India DLT' },
];

export class ExotelSmsProviderAdapter implements ProviderAdapter {
  public readonly id: string;
  public readonly channel: Channel = 'sms';
  public readonly fieldSchema = EXOTEL_SMS_SCHEMA;
  private fetchFn: typeof fetch;

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

  public async validateConfiguration(c: ResolvedCredentials): Promise<void> {
    if (!c.sid) throw new Error("Exotel SMS requires 'sid'");
    if (!c.api_key) throw new Error("Exotel SMS requires 'api_key'");
    if (!c.api_token) throw new Error("Exotel SMS requires 'api_token'");
    if (!c.sender_id) throw new Error("Exotel SMS requires DLT 'sender_id'");
    if (!c.pe_id) throw new Error("Exotel SMS requires DLT 'pe_id'");
  }

  public async send(
    m: NormalizedMessage,
    c: ResolvedCredentials,
    signal: AbortSignal,
  ): Promise<ProviderResult> {
    try {
      await this.validateConfiguration(c);
    } catch (err: any) {
      return { success: false, error: { code: 'CREDENTIAL_ERROR', message: err.message } };
    }

    const contentTemplate = m.content.template ? String(m.content.template) : '';
    const looksLikeDltId = /^\d{10,}$/.test(contentTemplate);
    const templateId =
      (m.metadata?.dlt_template_id as string | undefined) ||
      (looksLikeDltId ? contentTemplate : '') ||
      (c.template_id ? String(c.template_id) : '');

    if (!templateId) {
      return {
        success: false,
        error: {
          code: 'PROVIDER_REJECTED',
          message: 'Exotel India SMS requires a DLT template ID on the message or provider config',
        },
      };
    }

    const sid = String(c.sid);
    const apiKey = String(c.api_key);
    const apiToken = String(c.api_token);
    const host = String(c.subdomain || 'api.in.exotel.com').replace(/^https?:\/\//, '');
    const url = `https://${host}/v1/Accounts/${encodeURIComponent(sid)}/Sms/send.json`;

    const params = new URLSearchParams({
      From: String(c.sender_id),
      To: m.recipient,
      Body: m.content.body,
      DltEntityId: String(c.pe_id),
      DltTemplateId: templateId,
    });

    const auth = 'Basic ' + Buffer.from(`${apiKey}:${apiToken}`).toString('base64');

    try {
      const res = await this.fetchFn(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          Authorization: auth,
        },
        body: params.toString(),
        signal,
      });

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

      if (res.ok && (res.status === 200 || res.status === 201)) {
        const ref = data?.SMSMessage?.Sid || data?.Sid || `exo_${Date.now()}`;
        return {
          success: true,
          provider_reference: String(ref),
          raw_response: data,
        };
      }

      let code: ProviderErrorCode = 'PROVIDER_5XX';
      if (res.status === 401 || res.status === 403) code = 'CREDENTIAL_ERROR';
      else if (res.status >= 400 && res.status < 500) code = 'PROVIDER_REJECTED';

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

  public async healthCheck(c: ResolvedCredentials): Promise<ProviderHealth> {
    const start = Date.now();
    try {
      await this.validateConfiguration(c);
      return { status: 'healthy', latency_ms: Date.now() - start, checked_at: Date.now() };
    } catch (err: any) {
      return { status: 'down', latency_ms: Date.now() - start, checked_at: Date.now(), message: err.message };
    }
  }

  public parseWebhook(req: WebhookRequest): DeliveryEvent | null {
    const body = (req.body || req.query || {}) as Record<string, unknown>;
    const sid = body.SmsSid || body.Sid;
    const statusStr = String(body.Status || '').toLowerCase();
    if (!sid || !statusStr) return null;
    return {
      message_id: String(sid),
      status: statusStr === 'sent' || statusStr === 'delivered' ? 'delivered' : 'undelivered',
      timestamp: Date.now(),
      provider: this.id,
      raw: body,
    };
  }
}

const GUPSHUP_WA_SCHEMA: FieldSchema[] = [
  { key: 'api_key', label: 'API key', type: 'secret', required: true, section: 'Account' },
  { key: 'app', label: 'App name', type: 'text', required: true, section: 'Account' },
  { key: 'source', label: 'WhatsApp number', type: 'text', required: true, validator: 'phone', section: 'Account' },
];

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

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

  public async validateConfiguration(c: ResolvedCredentials): Promise<void> {
    if (!c.api_key) throw new Error("Gupshup WhatsApp requires 'api_key'");
    if (!c.app) throw new Error("Gupshup WhatsApp requires 'app'");
    if (!c.source) throw new Error("Gupshup WhatsApp requires 'source'");
  }

  public async send(
    m: NormalizedMessage,
    c: ResolvedCredentials,
    signal: AbortSignal,
  ): Promise<ProviderResult> {
    try {
      await this.validateConfiguration(c);
    } catch (err: any) {
      return { success: false, error: { code: 'CREDENTIAL_ERROR', message: err.message } };
    }

    const source = String(c.source).replace(/\D/g, '');
    const destination = m.recipient.replace(/\D/g, '');
    const appName = String(c.app);
    const templateId = 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 isTemplate = Boolean(templateId);
    const url = isTemplate
      ? 'https://api.gupshup.io/wa/api/v1/template/msg'
      : 'https://api.gupshup.io/wa/api/v1/msg';

    const params = new URLSearchParams({
      channel: 'whatsapp',
      source,
      destination,
      'src.name': appName,
    });

    const firstAtt = m.content.attachments?.[0];
    if (isTemplate) {
      params.set('template', JSON.stringify({ id: templateId, params: varValues }));
    } else if (firstAtt?.url) {
      const ct = (firstAtt.contentType || '').toLowerCase();
      const fn = (firstAtt.filename || '').toLowerCase();
      const gsType =
        ct.startsWith('image/') || /\.(png|jpe?g|webp|gif)$/.test(fn)
          ? 'image'
          : ct.startsWith('video/') || /\.(mp4|3gp|mov)$/.test(fn)
            ? 'video'
            : ct.startsWith('audio/') || /\.(mp3|ogg|aac|m4a)$/.test(fn)
              ? 'audio'
              : 'file';
      params.set(
        'message',
        JSON.stringify({
          type: gsType,
          ...(gsType === 'image'
            ? { originalUrl: firstAtt.url, previewUrl: firstAtt.url, caption: m.content.body }
            : { url: firstAtt.url, filename: firstAtt.filename, caption: m.content.body }),
        }),
      );
    } else {
      params.set('message', JSON.stringify({ type: 'text', text: m.content.body }));
    }

    try {
      const res = await this.fetchFn(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          apikey: String(c.api_key),
        },
        body: params.toString(),
        signal,
      });

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

      if (res.ok && (data.status === 'submitted' || data.messageId)) {
        return {
          success: true,
          provider_reference: String(data.messageId || `gs_wa_${Date.now()}`),
          raw_response: data,
        };
      }

      let code: ProviderErrorCode = 'PROVIDER_5XX';
      if (res.status === 401 || res.status === 403) code = 'CREDENTIAL_ERROR';
      else if (res.status >= 400 && res.status < 500) code = 'PROVIDER_REJECTED';

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

  public async healthCheck(c: ResolvedCredentials): Promise<ProviderHealth> {
    const start = Date.now();
    try {
      await this.validateConfiguration(c);
      return { status: 'healthy', latency_ms: Date.now() - start, checked_at: Date.now() };
    } catch (err: any) {
      return { status: 'down', latency_ms: Date.now() - start, checked_at: Date.now(), message: err.message };
    }
  }

  public parseWebhook(req: WebhookRequest): DeliveryEvent | null {
    const body = (req.body || {}) as Record<string, any>;
    const payload = body.payload || body;
    const msgId = payload.id || payload.gsId;
    const type = String(payload.type || body.type || '').toLowerCase();
    if (!msgId || !type) return null;
    const isDelivered = type === 'delivered' || type === 'read';
    return {
      message_id: String(msgId),
      status: isDelivered ? 'delivered' : 'undelivered',
      timestamp: Date.now(),
      provider: this.id,
      raw: body,
    };
  }
}

const D360_WA_SCHEMA: FieldSchema[] = [
  { key: 'api_key', label: 'D360 API key', type: 'secret', required: true },
  { key: 'base_url', label: 'Base URL', type: 'text', default: 'https://waba-v2.360dialog.io', validator: 'url' },
];

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

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

  public async validateConfiguration(c: ResolvedCredentials): Promise<void> {
    if (!c.api_key) throw new Error("360dialog WhatsApp requires 'api_key'");
  }

  public async send(
    m: NormalizedMessage,
    c: ResolvedCredentials,
    signal: AbortSignal,
  ): Promise<ProviderResult> {
    try {
      await this.validateConfiguration(c);
    } catch (err: any) {
      return { success: false, error: { code: 'CREDENTIAL_ERROR', message: err.message } };
    }

    const baseUrl = String(c.base_url || 'https://waba-v2.360dialog.io').replace(/\/$/, '');
    const url = `${baseUrl}/messages`;
    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 payload: Record<string, unknown>;
    if (templateName) {
      payload = {
        messaging_product: 'whatsapp',
        recipient_type: 'individual',
        to: recipientNumber,
        type: 'template',
        template: {
          name: templateName,
          language: { code: String(m.metadata?.language_code || 'en') },
          ...(varValues.length > 0
            ? {
                components: [
                  {
                    type: 'body',
                    parameters: varValues.map((text) => ({ type: 'text', text })),
                  },
                ],
              }
            : {}),
        },
      };
    } else if (firstAtt?.url) {
      const ct = (firstAtt.contentType || '').toLowerCase();
      const fn = (firstAtt.filename || '').toLowerCase();
      const kind =
        ct.startsWith('image/') || /\.(png|jpe?g|webp|gif)$/.test(fn)
          ? 'image'
          : ct.startsWith('video/') || /\.(mp4|3gp|mov)$/.test(fn)
            ? 'video'
            : ct.startsWith('audio/') || /\.(mp3|ogg|aac|m4a)$/.test(fn)
              ? 'audio'
              : 'document';
      payload = {
        messaging_product: 'whatsapp',
        recipient_type: 'individual',
        to: recipientNumber,
        type: kind,
        [kind]: {
          link: firstAtt.url,
          ...(m.content.body && kind !== 'audio' ? { caption: m.content.body } : {}),
          ...(kind === 'document' && firstAtt.filename ? { filename: firstAtt.filename } : {}),
        },
      };
    } else {
      payload = {
        messaging_product: 'whatsapp',
        recipient_type: 'individual',
        to: recipientNumber,
        type: 'text',
        text: { body: m.content.body },
      };
    }

    try {
      const res = await this.fetchFn(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'D360-API-KEY': String(c.api_key),
        },
        body: JSON.stringify(payload),
        signal,
      });

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

      if (res.ok && (res.status === 200 || res.status === 201)) {
        const ref = data.messages?.[0]?.id || `d360_${Date.now()}`;
        return {
          success: true,
          provider_reference: String(ref),
          raw_response: data,
        };
      }

      let code: ProviderErrorCode = 'PROVIDER_5XX';
      if (res.status === 401 || res.status === 403) code = 'CREDENTIAL_ERROR';
      else if (res.status >= 400 && res.status < 500) code = 'PROVIDER_REJECTED';

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

  public async healthCheck(c: ResolvedCredentials): Promise<ProviderHealth> {
    const start = Date.now();
    try {
      await this.validateConfiguration(c);
      return { status: 'healthy', latency_ms: Date.now() - start, checked_at: Date.now() };
    } catch (err: any) {
      return { status: 'down', latency_ms: Date.now() - start, checked_at: Date.now(), message: err.message };
    }
  }

  public parseWebhook(req: WebhookRequest): DeliveryEvent | null {
    const body = (req.body as any) || {};
    const statusObj = body.statuses?.[0] || body.entry?.[0]?.changes?.[0]?.value?.statuses?.[0];
    if (!statusObj?.id || !statusObj?.status) return null;
    const isDelivered = statusObj.status === 'delivered' || statusObj.status === 'read';
    return {
      message_id: String(statusObj.id),
      status: isDelivered ? 'delivered' : 'undelivered',
      timestamp: Number(statusObj.timestamp) * 1000 || Date.now(),
      provider: this.id,
      raw: statusObj,
    };
  }
}

export class TelegramBotProviderAdapter implements ProviderAdapter {
  public readonly id: string;
  public readonly channel: Channel;
  private fetchFn: typeof fetch;

  constructor(options: { id?: string; channel?: Channel; fetchFn?: typeof fetch } = {}) {
    this.id = options.id || 'telegram';
    this.channel = options.channel || ('chat' as Channel);
    this.fetchFn = options.fetchFn ?? globalThis.fetch.bind(globalThis);
  }

  public async validateConfiguration(c: ResolvedCredentials): Promise<void> {
    if (!c.bot_token) throw new Error("Telegram Bot requires 'bot_token'");
  }

  public async send(
    m: NormalizedMessage,
    c: ResolvedCredentials,
    signal: AbortSignal,
  ): Promise<ProviderResult> {
    try {
      await this.validateConfiguration(c);
    } catch (err: any) {
      return { success: false, error: { code: 'CREDENTIAL_ERROR', message: err.message } };
    }

    const token = String(c.bot_token);
    const url = `https://api.telegram.org/bot${encodeURIComponent(token)}/sendMessage`;

    try {
      const res = await this.fetchFn(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          chat_id: m.recipient,
          text: m.content.body,
        }),
        signal,
      });

      const data: any = await res.json().catch(() => ({}));
      if (res.ok && data.ok) {
        return {
          success: true,
          provider_reference: String(data.result?.message_id || `tg_${Date.now()}`),
          raw_response: data,
        };
      }

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

  public async healthCheck(c: ResolvedCredentials): Promise<ProviderHealth> {
    const start = Date.now();
    try {
      await this.validateConfiguration(c);
      return { status: 'healthy', latency_ms: Date.now() - start, checked_at: Date.now() };
    } catch (err: any) {
      return { status: 'down', latency_ms: Date.now() - start, checked_at: Date.now(), message: err.message };
    }
  }
}

const TWENTY_FOUR_SEVEN_SMS_SCHEMA: FieldSchema[] = [
  { key: 'api_key', label: 'API Key', type: 'secret', required: true, section: 'Account' },
  { key: 'sender_id', label: 'Sender ID (Header)', type: 'text', required: true, validator: 'header', dlt: true, section: 'India DLT' },
  { key: 'service_name', label: 'Service / Route Name', type: 'text', default: 'TEMPLATE_BASED', optional: true, section: 'Account' },
  { key: 'pe_id', label: 'Principal Entity ID', type: 'text', optional: true, validator: 'digits19', dlt: true, section: 'India DLT' },
  { key: 'template_id', label: 'Default DLT Template ID', type: 'text', optional: true, dlt: true, section: 'India DLT' },
  { key: 'host', label: 'API Host', type: 'text', default: 'websms.24x7sms.com', optional: true, validator: 'host', section: 'Account' },
];

export class TwentyFourSevenSmsProviderAdapter implements ProviderAdapter {
  public readonly id: string;
  public readonly channel: Channel = 'sms';
  public readonly fieldSchema = TWENTY_FOUR_SEVEN_SMS_SCHEMA;
  private fetchFn: typeof fetch;

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

  public async validateConfiguration(c: ResolvedCredentials): Promise<void> {
    if (!c.api_key) throw new Error("24x7SMS requires 'api_key'");
    if (!c.sender_id) throw new Error("24x7SMS requires DLT 'sender_id'");
  }

  public async send(
    m: NormalizedMessage,
    c: ResolvedCredentials,
    signal: AbortSignal,
  ): Promise<ProviderResult> {
    try {
      await this.validateConfiguration(c);
    } catch (err: any) {
      return { success: false, error: { code: 'CREDENTIAL_ERROR', message: err.message } };
    }

    const host = String(c.host || 'websms.24x7sms.com').replace(/^https?:\/\//, '').replace(/\/.*$/, '');
    const contentTemplate = m.content.template ? String(m.content.template) : '';
    const looksLikeDltId = /^\d{10,}$/.test(contentTemplate);
    const templateId =
      (m.metadata?.dlt_template_id as string | undefined) ||
      (looksLikeDltId ? contentTemplate : '') ||
      (c.template_id ? String(c.template_id) : '');

    const digits = m.recipient.replace(/\D/g, '');
    const toPhone = digits.length === 10 ? `91${digits}` : digits;

    const params = new URLSearchParams({
      APIKEY: String(c.api_key),
      MobileNo: toPhone,
      SenderID: String(c.sender_id),
      Message: m.content.body,
      ServiceName: String(c.service_name || 'TEMPLATE_BASED'),
    });

    if (templateId) {
      params.set('DLTTemplateID', templateId);
    }
    if (c.pe_id) {
      params.set('PE_ID', String(c.pe_id));
    }

    const url = `https://${host}/api_2.0/SendSMS.aspx?${params.toString()}`;

    try {
      const res = await this.fetchFn(url, {
        method: 'GET',
        signal,
      });

      const rawText = await res.text().catch(() => '');
      let data: any = {};
      try {
        data = JSON.parse(rawText);
      } catch {
        data = { text: rawText };
      }

      const lowerText = rawText.toLowerCase();
      const isError =
        !res.ok ||
        lowerText.includes('error') ||
        lowerText.includes('invalid') ||
        lowerText.includes('failed') ||
        lowerText.includes('insufficient') ||
        (data.status && String(data.status).toLowerCase() === 'error');

      if (!isError) {
        const ref =
          data.messageId ||
          data.jobId ||
          data.MessageID ||
          data.JobID ||
          (rawText.match(/\d{8,}/) ? rawText.match(/\d{8,}/)![0] : '') ||
          `247sms_${Date.now()}`;

        return {
          success: true,
          provider_reference: String(ref),
          raw_response: data,
        };
      }

      let code: ProviderErrorCode = 'PROVIDER_REJECTED';
      if (res.status >= 500) code = 'PROVIDER_5XX';
      else if (lowerText.includes('balance') || lowerText.includes('credit')) code = 'PROVIDER_REJECTED';
      else if (lowerText.includes('key') || lowerText.includes('auth') || lowerText.includes('password')) code = 'CREDENTIAL_ERROR';

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

  public async healthCheck(c: ResolvedCredentials): Promise<ProviderHealth> {
    const start = Date.now();
    try {
      await this.validateConfiguration(c);
      return { status: 'healthy', latency_ms: Date.now() - start, checked_at: Date.now() };
    } catch (err: any) {
      return { status: 'down', latency_ms: Date.now() - start, checked_at: Date.now(), message: err.message };
    }
  }

  public parseWebhook(req: WebhookRequest): DeliveryEvent | null {
    const body = (req.body || req.query || {}) as Record<string, unknown>;
    const msgId = body.msgId || body.messageId || body.jobId || body.id;
    const statusStr = String(body.status || '').toUpperCase();
    if (!msgId || !statusStr) return null;
    const isDelivered = statusStr === 'DELIVERED' || statusStr === 'SUCCESS';
    return {
      message_id: String(msgId),
      status: isDelivered ? 'delivered' : 'undelivered',
      timestamp: Date.now(),
      provider: this.id,
      raw: body,
    };
  }
}
