import type { CapabilityProvider, CapabilityRequest, ProviderResult } from '@insignia/iios-contracts';

/** The decrypted Twilio config a tenant configures (BYO credentials). */
export interface TwilioCreds {
  accountSid: string;
  authToken: string;
  fromNumber: string;
}

/** Resolves a scope's Twilio creds (decrypting on demand); null when unconfigured/disabled. */
export type TwilioCredResolver = (scopeId: string) => Promise<TwilioCreds | null>;

/** Injectable HTTP sender so tests don't hit the network; prod uses fetch. */
export type HttpSender = (url: string, init: { method: string; headers: Record<string, string>; body: string }) => Promise<{
  ok: boolean;
  status: number;
  json: () => Promise<unknown>;
  text: () => Promise<string>;
}>;

const defaultSender: HttpSender = (url, init) => fetch(url, init) as unknown as ReturnType<HttpSender>;

/**
 * SMS egress via Twilio, using the CALLER'S OWN credentials (resolved per scope at send time).
 * Fail-closed: no scope or no configured creds ⇒ FAILED/NOT_CONFIGURED, nothing sent. A Twilio
 * API error is surfaced as FAILED (never thrown), per the provider doctrine.
 */
export class TwilioSmsProvider implements CapabilityProvider {
  readonly name = 'twilio-sms';
  readonly channelTypes = ['SMS'];
  readonly capabilities = { canSend: true, maxPayloadBytes: 1600 };

  constructor(
    private readonly resolve: TwilioCredResolver,
    private readonly http: HttpSender = defaultSender,
  ) {}

  async send(req: CapabilityRequest): Promise<ProviderResult> {
    const started = Date.now();
    if (!req.scopeId) return { providerRef: 'unconfigured', outcome: 'FAILED', errorCode: 'NOT_CONFIGURED' };

    const creds = await this.resolve(req.scopeId).catch(() => null);
    if (!creds) return { providerRef: 'unconfigured', outcome: 'FAILED', errorCode: 'NOT_CONFIGURED' };

    const body = new URLSearchParams({
      To: req.target,
      From: creds.fromNumber,
      Body: String(req.payload.text ?? req.payload.body ?? ''),
    }).toString();

    try {
      const res = await this.http(`https://api.twilio.com/2010-04-01/Accounts/${creds.accountSid}/Messages.json`, {
        method: 'POST',
        headers: {
          authorization: `Basic ${Buffer.from(`${creds.accountSid}:${creds.authToken}`).toString('base64')}`,
          'content-type': 'application/x-www-form-urlencoded',
        },
        body,
      });
      const latencyMs = Date.now() - started;
      const payload = (await res.json().catch(() => ({}))) as { sid?: string; code?: number; message?: string };
      if (!res.ok) {
        return { providerRef: `twilio-${res.status}`, outcome: 'FAILED', errorCode: payload.code ? `TWILIO_${payload.code}` : `HTTP_${res.status}`, latencyMs };
      }
      return { providerRef: payload.sid ?? 'twilio-sent', outcome: 'SENT', latencyMs };
    } catch (err) {
      return { providerRef: 'twilio-error', outcome: 'FAILED', errorCode: (err as Error).message.slice(0, 60), latencyMs: Date.now() - started };
    }
  }
}
