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

const HTTP_FIELD_SCHEMA: FieldSchema[] = [
  {
    key: 'url',
    label: 'Endpoint URL',
    type: 'text',
    required: true,
    placeholder: 'https://api.example.com/send',
    validator: 'url',
  },
  {
    key: 'auth',
    label: 'Authorization header',
    type: 'secret',
    optional: true,
    placeholder: 'Bearer …',
  },
  {
    key: 'body',
    label: 'Body template',
    type: 'textarea',
    required: true,
    placeholder: '{"to":"{{to}}","text":"{{body}}"}',
  },
];

function renderTemplate(template: string, msg: NormalizedMessage): string {
  return template
    .replace(/\{\{to\}\}/g, msg.recipient)
    .replace(/\{\{body\}\}/g, msg.content.body)
    .replace(/\{\{subject\}\}/g, msg.content.subject || '')
    .replace(/\{\{message_id\}\}/g, msg.message_id);
}

export class GenericHttpProvider implements ProviderAdapter {
  public readonly id: string;
  public readonly channel: Channel;
  public readonly fieldSchema = HTTP_FIELD_SCHEMA;
  private fetchFn: typeof fetch;

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

  public async validateConfiguration(c: ResolvedCredentials): Promise<void> {
    if (!c.url || String(c.url).trim() === '') {
      throw new Error("Custom HTTP provider requires 'url'");
    }
    if (!c.body || String(c.body).trim() === '') {
      throw new Error("Custom HTTP provider requires 'body' template");
    }
  }

  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 url = String(c.url);
    const bodyStr = renderTemplate(String(c.body), m);
    const headers: Record<string, string> = { 'Content-Type': 'application/json' };
    if (c.auth) {
      headers.Authorization = String(c.auth);
    }

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

      let parsed: Record<string, unknown> = {};
      const text = await res.text();
      try {
        parsed = JSON.parse(text) as Record<string, unknown>;
      } catch {
        parsed = { raw: text.slice(0, 200) };
      }

      if (res.ok) {
        return {
          success: true,
          provider_reference: String(parsed.id || parsed.message_id || `http_${Date.now()}`),
          raw_response: parsed,
        };
      }

      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 (res.status === 429) code = 'PROVIDER_UNAVAILABLE';

      return {
        success: false,
        error: {
          code,
          message: `HTTP provider returned status ${res.status}`,
          raw: parsed,
        },
      };
    } catch (err: any) {
      const isTimeout = signal.aborted || err.name === 'AbortError';
      return {
        success: false,
        error: {
          code: isTimeout ? 'TIMEOUT' : 'PROVIDER_5XX',
          message: err.message || 'Custom HTTP 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(),
        message: 'Custom HTTP endpoint configured',
      };
    } catch (err: any) {
      return {
        status: 'down',
        latency_ms: Date.now() - start,
        checked_at: Date.now(),
        message: err.message,
      };
    }
  }
}
