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

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

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

  public async validateConfiguration(c: ResolvedCredentials): Promise<void> {
    if (!c.api_key && !c.apiKey) {
      throw new Error("SendGrid configuration missing 'api_key'");
    }
  }

  public async send(
    m: NormalizedMessage,
    c: ResolvedCredentials,
    signal: AbortSignal
  ): Promise<ProviderResult> {
    const apiKey = String(c.api_key || c.apiKey || '');
    const fromAddress = String(c.from || 'noreply@zyphon.local');

    const content: Array<{ type: string; value: string }> = [];
    if (m.content.type === 'html') {
      content.push({ type: 'text/html', value: m.content.body });
    } else {
      content.push({ type: 'text/plain', value: m.content.body });
    }

    const payload: Record<string, unknown> = {
      personalizations: [
        {
          to: [{ email: m.recipient }],
          custom_args: { zyphon_message_id: m.message_id },
        },
      ],
      from: { email: fromAddress },
      subject: m.content.subject || '(No Subject)',
      content,
    };

    if (m.content.attachments && m.content.attachments.length > 0) {
      payload.attachments = m.content.attachments.map((a) => ({
        content: a.content,
        filename: a.filename,
        type: a.contentType,
        disposition: 'attachment',
      }));
    }

    try {
      const res = await this.fetchFn('https://api.sendgrid.com/v3/mail/send', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${apiKey}`,
        },
        body: JSON.stringify(payload),
        signal,
      });

      if (res.status === 202 || res.status === 200) {
        const messageIdHeader = res.headers.get('x-message-id') || `sg_${Date.now()}`;
        return {
          success: true,
          provider_reference: messageIdHeader,
          raw_response: { status: res.status, headers: Object.fromEntries(res.headers.entries()) },
        };
      }

      let errData: any = {};
      try {
        errData = await res.json();
      } catch {}

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

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

  public async healthCheck(c: ResolvedCredentials): Promise<ProviderHealth> {
    const startTime = Date.now();
    const apiKey = String(c.api_key || c.apiKey || '');
    if (!apiKey) {
      return { status: 'down', latency_ms: 0, checked_at: Date.now(), message: 'Missing API key' };
    }

    try {
      const res = await this.fetchFn('https://api.sendgrid.com/v3/scopes', {
        method: 'GET',
        headers: { Authorization: `Bearer ${apiKey}` },
      });

      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 events = Array.isArray(req.body) ? req.body : [req.body];
    for (const ev of events) {
      if (ev && typeof ev === 'object') {
        const msgId = ev.zyphon_message_id || ev.sg_message_id;
        const eventType = ev.event;
        if (msgId && eventType) {
          const isDelivered = eventType === 'delivered';
          return {
            message_id: msgId,
            status: isDelivered ? 'delivered' : 'undelivered',
            timestamp: (ev.timestamp ? ev.timestamp * 1000 : Date.now()),
            provider: this.id,
            raw: ev,
          };
        }
      }
    }
    return null;
  }
}

