import { CarrierApi, CarrierCall, CarrierSms, CarrierDecision } from './carrier-types';

interface TwilioConfig {
  apiKey: string;
  apiSecret: string;
  accountSid: string;
  apiBaseUrl?: string;
  decisionTimeout?: number;
}

export class TwilioCarrier implements CarrierApi {
  private readonly config: TwilioConfig;
  private readonly apiBaseUrl: string;
  
  constructor(config: TwilioConfig) {
    this.config = {
      ...config,
      apiBaseUrl: config.apiBaseUrl ?? 'https://api.twilio.com',
      decisionTimeout: config.decisionTimeout ?? 200,
    };
    this.apiBaseUrl = this.config.apiBaseUrl;
  }

  async getCall(callSid: string): Promise<CarrierCall> {
    const response = await fetch(
      `${this.apiBaseUrl}/2010-04-01/Accounts/${this.config.accountSid}/Calls/${callSid}.json`,
      {
        method: 'GET',
        headers: {
          'Authorization': `Basic ${Buffer.from(`${this.config.apiKey}:${this.config.apiSecret}`).toString('base64')}`,
          'Accept': 'application/json',
        },
        timeout: this.config.decisionTimeout,
      }
    );

    if (!response.ok) {
      throw new Error(`Twilio API error: ${response.status}`);
    }

    const data = await response.json() as TwilioCallResponse;
    return this.mapToCarrierCall(data);
  }

  async blockCall(callSid: string): Promise<void> {
    await this.executeCarrierAction('block', callSid);
  }

  async flagCall(callSid: string): Promise<void> {
    await this.executeCarrierAction('flag', callSid);
  }

  async allowCall(callSid: string): Promise<void> {
    await this.executeCarrierAction('allow', callSid);
  }

  async getSms(messageSid: string): Promise<CarrierSms> {
    const response = await fetch(
      `${this.apiBaseUrl}/2010-04-01/Accounts/${this.config.accountSid}/Messages/${messageSid}.json`,
      {
        method: 'GET',
        headers: {
          'Authorization': `Basic ${Buffer.from(`${this.config.apiKey}:${this.config.apiSecret}`).toString('base64')}`,
          'Accept': 'application/json',
        },
        timeout: this.config.decisionTimeout,
      }
    );

    if (!response.ok) {
      throw new Error(`Twilio API error: ${response.status}`);
    }

    const data = await response.json() as TwilioSmsResponse;
    return this.mapToCarrierSms(data);
  }

  async blockSms(messageSid: string): Promise<void> {
    await this.executeCarrierAction('block', messageSid, 'sms');
  }

  async flagSms(messageSid: string): Promise<void> {
    await this.executeCarrierAction('flag', messageSid, 'sms');
  }

  async allowSms(messageSid: string): Promise<void> {
    await this.executeCarrierAction('allow', messageSid, 'sms');
  }

  async isHealthy(): Promise<boolean> {
    try {
      const response = await fetch(
        `${this.apiBaseUrl}/2010-04-01/Accounts/${this.config.accountSid}.json`,
        {
          method: 'GET',
          headers: {
            'Authorization': `Basic ${Buffer.from(`${this.config.apiKey}:${this.config.apiSecret}`).toString('base64')}`,
          },
          timeout: 5000,
        }
      );
      return response.ok;
    } catch {
      return false;
    }
  }

  private async executeCarrierAction(
    action: 'block' | 'flag' | 'allow',
    sid: string,
    type: 'call' | 'sms' = 'call'
  ): Promise<void> {
    const endpoint = type === 'call' 
      ? `${this.apiBaseUrl}/2010-04-01/Accounts/${this.config.accountSid}/Calls/${sid}.json`
      : `${this.apiBaseUrl}/2010-04-01/Accounts/${this.config.accountSid}/Messages/${sid}.json`;

    // Twilio uses Status parameter to control call/SMS state
    const statusUpdate: string = 
      action === 'block' ? 'completed' : 
      action === 'flag' ? 'ringing' : 'in-progress';

    await fetch(endpoint, {
      method: 'POST',
      headers: {
        'Authorization': `Basic ${Buffer.from(`${this.config.apiKey}:${this.config.apiSecret}`).toString('base64')}`,
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: `Status=${statusUpdate}`,
      timeout: this.config.decisionTimeout,
    });
  }

  private mapToCarrierCall(data: TwilioCallResponse): CarrierCall {
    return {
      callSid: data.sid,
      from: data.from,
      to: data.to,
      status: this.mapCallStatus(data.status),
      startTime: new Date(data.startTime),
      duration: data.duration ? parseInt(data.duration) : undefined,
      metadata: {
        twilioPrice: data.price,
        twilioDirection: data.direction,
        twilioApiVersion: data.apiVersion,
      },
    };
  }

  private mapToCarrierSms(data: TwilioSmsResponse): CarrierSms {
    return {
      messageSid: data.sid,
      from: data.from,
      to: data.to,
      body: data.body,
      direction: this.mapSmsDirection(data.direction),
      status: this.mapSmsStatus(data.status),
      timestamp: new Date(data.dateSent || data.dateCreated),
      metadata: {
        twilioNumSegments: data.numSegments,
        twilioNumMedia: data.numMedia,
        twilioError: data.errorMessage,
      },
    };
  }

  private mapCallStatus(status: string): CarrierCall['status'] {
    const statusMap: Record<string, CarrierCall['status']> = {
      'initiated': 'initiated',
      'ringing': 'ringing',
      'in-progress': 'in-progress',
      'completed': 'completed',
      'failed': 'failed',
      'busy': 'failed',
      'no-answer': 'failed',
    };
    return statusMap[status] ?? 'failed';
  }

  private mapSmsDirection(direction: string): CarrierSms['direction'] {
    return direction === 'inbound' ? 'inbound' : 'outbound';
  }

  private mapSmsStatus(status: string): CarrierSms['status'] {
    const statusMap: Record<string, CarrierSms['status']> = {
      'queued': 'queued',
      'sent': 'sent',
      'delivered': 'delivered',
      'failed': 'failed',
      'undelivered': 'failed',
    };
    return statusMap[status] ?? 'failed';
  }
}

interface TwilioCallResponse {
  sid: string;
  from: string;
  to: string;
  status: string;
  startTime: string;
  duration?: string;
  price?: string;
  direction?: string;
  apiVersion?: string;
}

interface TwilioSmsResponse {
  sid: string;
  from: string;
  to: string;
  body: string;
  direction: string;
  status: string;
  dateSent?: string;
  dateCreated: string;
  numSegments?: string;
  numMedia?: string;
  errorMessage?: string;
}
