import { describe, it, expect, vi } from 'vitest';
import type { CapabilityRequest } from '@insignia/iios-contracts';
import { TwilioSmsProvider, type TwilioCreds } from './twilio-sms.provider';

const CREDS: TwilioCreds = { accountSid: 'AC123', authToken: 'tok_secret', fromNumber: '+15550001111' };

const req = (over: Partial<CapabilityRequest> = {}): CapabilityRequest => ({
  capability: 'channel.send',
  channelType: 'SMS',
  scopeId: 'scope_1',
  target: '+15557654321',
  payload: { text: 'hello world' },
  idempotencyKey: 'idem-1',
  ...over,
});

describe('TwilioSmsProvider', () => {
  it('resolves the scope creds and POSTs to Twilio, returning SENT with the message SID', async () => {
    const http = vi.fn().mockResolvedValue({ ok: true, status: 201, json: async () => ({ sid: 'SM999' }), text: async () => '' });
    const resolve = vi.fn().mockResolvedValue(CREDS);
    const p = new TwilioSmsProvider(resolve, http);

    const res = await p.send(req());

    expect(resolve).toHaveBeenCalledWith('scope_1');
    const [url, init] = http.mock.calls[0];
    expect(url).toBe('https://api.twilio.com/2010-04-01/Accounts/AC123/Messages.json');
    expect(init.method).toBe('POST');
    expect(init.headers.authorization).toBe(`Basic ${Buffer.from('AC123:tok_secret').toString('base64')}`);
    const body = new URLSearchParams(init.body as string);
    expect(body.get('To')).toBe('+15557654321');
    expect(body.get('From')).toBe('+15550001111');
    expect(body.get('Body')).toBe('hello world');
    expect(res).toMatchObject({ outcome: 'SENT', providerRef: 'SM999' });
  });

  it('fails closed as NOT_CONFIGURED when the scope has no creds', async () => {
    const http = vi.fn();
    const p = new TwilioSmsProvider(async () => null, http);
    const res = await p.send(req());
    expect(res.outcome).toBe('FAILED');
    expect(res.errorCode).toBe('NOT_CONFIGURED');
    expect(http).not.toHaveBeenCalled();
  });

  it('fails closed as NOT_CONFIGURED when the request has no scope', async () => {
    const p = new TwilioSmsProvider(async () => CREDS, vi.fn());
    const res = await p.send(req({ scopeId: undefined }));
    expect(res.outcome).toBe('FAILED');
    expect(res.errorCode).toBe('NOT_CONFIGURED');
  });

  it('surfaces a Twilio API error as FAILED (never throws)', async () => {
    const http = vi.fn().mockResolvedValue({ ok: false, status: 401, json: async () => ({ code: 20003, message: 'Authenticate' }), text: async () => 'Authenticate' });
    const p = new TwilioSmsProvider(async () => CREDS, http);
    const res = await p.send(req());
    expect(res.outcome).toBe('FAILED');
    expect(res.errorCode).toBe('TWILIO_20003');
  });

  it('surfaces a transport throw as FAILED', async () => {
    const http = vi.fn().mockRejectedValue(new Error('network down'));
    const p = new TwilioSmsProvider(async () => CREDS, http);
    const res = await p.send(req());
    expect(res.outcome).toBe('FAILED');
  });
});
