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

const MAILGUN_FIELD_SCHEMA: FieldSchema[] = [
  { key: 'api_key', label: 'API key', type: 'secret', required: true },
  { key: 'domain', label: 'Sending domain', type: 'text', required: true, placeholder: 'mg.company.com' },
  {
    key: 'from',
    label: 'Send emails as',
    type: 'text',
    required: true,
    validator: 'email',
    placeholder: 'noreply@company.com',
  },
  { key: 'region', label: 'API region', type: 'select', default: 'us', options: [
    { value: 'us', label: 'US' },
    { value: 'eu', label: 'EU' },
  ]},
];

export class MailgunProviderAdapter implements ProviderAdapter {
  public readonly id: string;
  public readonly channel: Channel = 'email';
  public readonly fieldSchema = MAILGUN_FIELD_SCHEMA;
  private fetchFn: typeof fetch;

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

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

  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 region = String(c.region || 'us');
    const host = region === 'eu' ? 'api.eu.mailgun.net' : 'api.mailgun.net';
    const domain = String(c.domain);
    const url = `https://${host}/v3/${encodeURIComponent(domain)}/messages`;

    const auth = 'Basic ' + Buffer.from(`api:${String(c.api_key)}`).toString('base64');
    const hasAttachments = Boolean(m.content.attachments && m.content.attachments.length > 0);

    try {
      let body: string | FormData;
      const headers: Record<string, string> = { Authorization: auth };

      if (hasAttachments) {
        const form = new FormData();
        form.set('from', String(c.from));
        form.set('to', m.recipient);
        form.set('subject', m.content.subject || '(No Subject)');
        if (m.content.type === 'html') {
          form.set('html', m.content.body);
        } else {
          form.set('text', m.content.body);
        }
        form.set('h:X-Zyphon-Message-Id', m.message_id);
        for (const att of m.content.attachments || []) {
          if (!att.content) continue;
          const bytes = new Uint8Array(Buffer.from(att.content, 'base64'));
          form.append(
            'attachment',
            new Blob([bytes], { type: att.contentType || 'application/octet-stream' }),
            att.filename,
          );
        }
        body = form;
      } else {
        const form = new URLSearchParams();
        form.set('from', String(c.from));
        form.set('to', m.recipient);
        form.set('subject', m.content.subject || '(No Subject)');
        if (m.content.type === 'html') {
          form.set('html', m.content.body);
        } else {
          form.set('text', m.content.body);
        }
        form.set('h:X-Zyphon-Message-Id', m.message_id);
        headers['Content-Type'] = 'application/x-www-form-urlencoded';
        body = form.toString();
      }

      const res = await this.fetchFn(url, {
        method: 'POST',
        headers,
        body,
        signal,
      });

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

      if (res.ok && data.id) {
        return {
          success: true,
          provider_reference: String(data.id),
          raw_response: { id: data.id, message: data.message },
        };
      }

      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 || `Mailgun 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 || 'Mailgun 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,
      };
    }
  }
}
