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

export class SesProviderAdapter 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 || 'ses';
    this.fetchFn = options.fetchFn ?? globalThis.fetch.bind(globalThis);
  }

  public async validateConfiguration(c: ResolvedCredentials): Promise<void> {
    const key = c.access_key || c.access_key_id || c.accessKeyId;
    const secret = c.secret || c.secret_access_key || c.secretAccessKey;
    if (!key || !secret) {
      throw new Error("AWS SES configuration requires 'access_key' and 'secret'");
    }
  }

  private signV4(
    method: string,
    url: URL,
    headers: Record<string, string>,
    body: string,
    accessKeyId: string,
    secretAccessKey: string,
    region: string
  ): Record<string, string> {
    const service = 'ses';
    const now = new Date();
    const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, '');
    const dateStamp = amzDate.slice(0, 8);

    headers['host'] = url.host;
    headers['x-amz-date'] = amzDate;

    const payloadHash = crypto.createHash('sha256').update(body, 'utf8').digest('hex');
    headers['x-amz-content-sha256'] = payloadHash;

    const signedHeaderKeys = Object.keys(headers).map((k) => k.toLowerCase()).sort();
    const canonicalHeaders = signedHeaderKeys.map((k) => `${k}:${headers[k]?.trim()}\n`).join('');
    const signedHeaders = signedHeaderKeys.join(';');

    const canonicalRequest = [
      method,
      url.pathname,
      url.search ? url.search.slice(1) : '',
      canonicalHeaders,
      signedHeaders,
      payloadHash,
    ].join('\n');

    const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
    const stringToSign = [
      'AWS4-HMAC-SHA256',
      amzDate,
      credentialScope,
      crypto.createHash('sha256').update(canonicalRequest, 'utf8').digest('hex'),
    ].join('\n');

    const kDate = crypto.createHmac('sha256', `AWS4${secretAccessKey}`).update(dateStamp).digest();
    const kRegion = crypto.createHmac('sha256', kDate).update(region).digest();
    const kService = crypto.createHmac('sha256', kRegion).update(service).digest();
    const kSigning = crypto.createHmac('sha256', kService).update('aws4_request').digest();
    const signature = crypto.createHmac('sha256', kSigning).update(stringToSign).digest('hex');

    const authHeader = `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
    headers['authorization'] = authHeader;

    return headers;
  }

  public async send(
    m: NormalizedMessage,
    c: ResolvedCredentials,
    signal: AbortSignal
  ): Promise<ProviderResult> {
    const region = String(c.region || 'ap-south-1');
    const accessKeyId = String(c.access_key || c.access_key_id || c.accessKeyId || '');
    const secretAccessKey = String(c.secret || c.secret_access_key || c.secretAccessKey || '');
    const fromAddress = String(c.from || 'noreply@zyphon.local');

    const url = new URL(`https://email.${region}.amazonaws.com/v2/email/outbound-emails`);

    const bodyObj = {
      FromEmailAddress: fromAddress,
      Destination: {
        ToAddresses: [m.recipient],
      },
      Content: {
        Simple: {
          Subject: { Data: m.content.subject || '(No Subject)', Charset: 'UTF-8' },
          Body: {
            Text: m.content.type === 'text' ? { Data: m.content.body, Charset: 'UTF-8' } : undefined,
            Html: m.content.type === 'html' ? { Data: m.content.body, Charset: 'UTF-8' } : undefined,
          },
        },
      },
      EmailTags: [{ Name: 'zyphon_message_id', Value: m.message_id }],
    };

    const bodyStr = JSON.stringify(bodyObj);
    const headers: Record<string, string> = {
      'content-type': 'application/json',
    };

    const signedHeaders = this.signV4('POST', url, headers, bodyStr, accessKeyId, secretAccessKey, region);

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

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

      if (res.status === 200) {
        return {
          success: true,
          provider_reference: resData.MessageId || `ses_${Date.now()}`,
          raw_response: resData,
        };
      }

      let code: ProviderErrorCode = 'PROVIDER_5XX';
      if (res.status === 403 || res.status === 401) 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.message || resData.Message || `SES 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 || 'AWS SES dispatch failed',
        },
      };
    }
  }

  public async healthCheck(c: ResolvedCredentials): Promise<ProviderHealth> {
    const startTime = Date.now();
    const region = String(c.region || 'ap-south-1');
    const accessKeyId = String(c.access_key || c.access_key_id || c.accessKeyId || '');
    const secretAccessKey = String(c.secret || c.secret_access_key || c.secretAccessKey || '');

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

    const url = new URL(`https://email.${region}.amazonaws.com/v2/email/account`);
    const headers: Record<string, string> = { 'content-type': 'application/json' };
    const signedHeaders = this.signV4('GET', url, headers, '', accessKeyId, secretAccessKey, region);

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

      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 {
      // AWS SNS notification payload wrapping SES event
      const msg = typeof body.Message === 'string' ? JSON.parse(body.Message) : body;
      const eventType = msg.eventType || msg.notificationType;
      const mailId = msg.mail?.messageId;

      if (mailId && eventType) {
        const isDelivered = eventType === 'Delivery' || eventType === 'delivery';
        return {
          message_id: mailId,
          status: isDelivered ? 'delivered' : 'undelivered',
          timestamp: Date.now(),
          provider: this.id,
          raw: msg,
        };
      }
    } catch {}

    return null;
  }
}

