import { Injectable, Logger } from '@nestjs/common';
import axios, { AxiosInstance } from 'axios';

interface SignalAccount {
  number: string;
  uuid: string;
  username?: string;
}

export interface SignalStatus {
  isConnected: boolean;
  isLinked: boolean;
  phoneNumber: string | null;
  error?: string;
}

export interface QRCodeResponse {
  qrcode: string;
  expiresAt?: number;
}

@Injectable()
export class SignalService {
  private readonly logger = new Logger(SignalService.name);
  private readonly client: AxiosInstance;
  private readonly baseUrl: string;

  constructor() {
    this.baseUrl = process.env.SIGNAL_API_URL || 'http://localhost:8080';
    this.client = axios.create({
      baseURL: this.baseUrl,
      timeout: 30000,
    });
  }

  /**
   * Check if Signal API is available and get connection status
   */
  async getStatus(): Promise<SignalStatus> {
    try {
      // Check if API is reachable
      const response = await this.client.get('/v1/about');

      // Try to get registered accounts
      // API returns array of phone number strings: ["+1234567890"]
      const accountsResponse = await this.client.get('/v1/accounts');
      const accounts: string[] = accountsResponse.data;

      if (accounts.length > 0) {
        return {
          isConnected: true,
          isLinked: true,
          phoneNumber: accounts[0],
        };
      }

      return {
        isConnected: true,
        isLinked: false,
        phoneNumber: null,
      };
    } catch (error: any) {
      this.logger.error('Failed to connect to Signal API:', error.message);
      return {
        isConnected: false,
        isLinked: false,
        phoneNumber: null,
        error: error.code === 'ECONNREFUSED'
          ? 'Signal API container is not running'
          : error.message,
      };
    }
  }

  /**
   * Get QR code for linking a new device
   */
  async getQRCodeLink(deviceName: string = 'VIP Coordinator'): Promise<QRCodeResponse | null> {
    try {
      // First check if already linked
      const status = await this.getStatus();
      if (status.isLinked) {
        this.logger.warn('Device already linked to Signal');
        return null;
      }

      // Request QR code for device linking - returns raw PNG image
      const response = await this.client.get('/v1/qrcodelink', {
        params: { device_name: deviceName },
        timeout: 60000, // QR generation can take a moment
        responseType: 'arraybuffer', // Get raw binary data
      });

      // Convert to base64
      const base64 = Buffer.from(response.data, 'binary').toString('base64');

      return {
        qrcode: base64,
      };
    } catch (error: any) {
      this.logger.error('Failed to get QR code:', error.message);
      throw error;
    }
  }

  /**
   * Register a new phone number (requires verification)
   */
  async registerNumber(phoneNumber: string, captcha?: string): Promise<{
    success: boolean;
    message: string;
    captchaRequired?: boolean;
    captchaUrl?: string;
  }> {
    try {
      const response = await this.client.post(`/v1/register/${phoneNumber}`, {
        captcha,
        use_voice: false,
      });

      return {
        success: true,
        message: 'Verification code sent. Check your phone.',
      };
    } catch (error: any) {
      const errorMessage = error.response?.data?.error || error.message;
      this.logger.error('Failed to register number:', errorMessage);

      // Check if CAPTCHA is required
      const isCaptchaRequired =
        errorMessage.toLowerCase().includes('captcha') ||
        error.response?.status === 402; // Signal uses 402 for captcha requirement

      if (isCaptchaRequired) {
        return {
          success: false,
          captchaRequired: true,
          captchaUrl: 'https://signalcaptchas.org/registration/generate.html',
          message:
            'CAPTCHA verification required. Please solve the CAPTCHA and submit the token.',
        };
      }

      return {
        success: false,
        message: errorMessage,
      };
    }
  }

  /**
   * Verify a phone number with the code received
   */
  async verifyNumber(phoneNumber: string, verificationCode: string): Promise<{ success: boolean; message: string }> {
    try {
      const response = await this.client.post(`/v1/register/${phoneNumber}/verify/${verificationCode}`);

      return {
        success: true,
        message: 'Phone number verified and linked successfully!',
      };
    } catch (error: any) {
      this.logger.error('Failed to verify number:', error.message);
      return {
        success: false,
        message: error.response?.data?.error || error.message,
      };
    }
  }

  /**
   * Unlink/unregister the current account
   */
  async unlinkAccount(phoneNumber: string): Promise<{ success: boolean; message: string }> {
    try {
      // Use POST /v1/unregister/{number} - the correct Signal API endpoint
      await this.client.post(`/v1/unregister/${phoneNumber}`);

      return {
        success: true,
        message: 'Account unlinked successfully',
      };
    } catch (error: any) {
      this.logger.error('Failed to unlink account:', error.message);
      return {
        success: false,
        message: error.response?.data?.error || error.message,
      };
    }
  }

  /**
   * Send a message to a recipient
   */
  async sendMessage(
    fromNumber: string,
    toNumber: string,
    message: string,
  ): Promise<{ success: boolean; timestamp?: number; error?: string }> {
    try {
      const response = await this.client.post(`/v2/send`, {
        number: fromNumber,
        recipients: [toNumber],
        message,
      });

      this.logger.log(`Message sent to ${toNumber}`);
      return {
        success: true,
        timestamp: response.data.timestamp,
      };
    } catch (error: any) {
      this.logger.error(`Failed to send message to ${toNumber}:`, error.message);
      return {
        success: false,
        error: error.response?.data?.error || error.message,
      };
    }
  }

  /**
   * Send a message to multiple recipients
   */
  async sendBulkMessage(
    fromNumber: string,
    toNumbers: string[],
    message: string,
  ): Promise<{ success: boolean; sent: number; failed: number; errors: string[] }> {
    const results = {
      success: true,
      sent: 0,
      failed: 0,
      errors: [] as string[],
    };

    for (const toNumber of toNumbers) {
      const result = await this.sendMessage(fromNumber, toNumber, message);
      if (result.success) {
        results.sent++;
      } else {
        results.failed++;
        results.errors.push(`${toNumber}: ${result.error}`);
      }
    }

    results.success = results.failed === 0;
    return results;
  }

  /**
   * Get the linked phone number (if any)
   */
  async getLinkedNumber(): Promise<string | null> {
    try {
      const response = await this.client.get('/v1/accounts');
      // API returns array of phone number strings directly: ["+1234567890"]
      const accounts: string[] = response.data;

      if (accounts.length > 0) {
        return accounts[0];
      }
      return null;
    } catch (error) {
      return null;
    }
  }

  /**
   * Format phone number for Signal (must include country code)
   */
  formatPhoneNumber(phone: string): string {
    // Remove all non-digit characters
    let cleaned = phone.replace(/\D/g, '');

    // Add US country code if not present
    if (cleaned.length === 10) {
      cleaned = '1' + cleaned;
    }

    // Add + prefix
    if (!cleaned.startsWith('+')) {
      cleaned = '+' + cleaned;
    }

    return cleaned;
  }

  /**
   * Receive pending messages for the account
   * This fetches and removes messages from Signal's queue
   */
  async receiveMessages(phoneNumber: string): Promise<any[]> {
    try {
      const response = await this.client.get(`/v1/receive/${phoneNumber}`, {
        timeout: 10000,
      });

      // Response is an array of message envelopes
      return response.data || [];
    } catch (error: any) {
      // Don't log timeout errors or empty responses as errors
      if (error.code === 'ECONNABORTED' || error.response?.status === 204) {
        return [];
      }
      throw error;
    }
  }

  /**
   * Send a message with a file attachment (PDF, image, etc.)
   * @param fromNumber - The sender's phone number
   * @param toNumber - The recipient's phone number
   * @param message - Optional text message to accompany the attachment
   * @param attachment - Base64 encoded file data
   * @param filename - Name for the file
   * @param mimeType - MIME type of the file (e.g., 'application/pdf')
   */
  async sendMessageWithAttachment(
    fromNumber: string,
    toNumber: string,
    message: string,
    attachment: string,
    filename: string,
    mimeType: string = 'application/pdf',
  ): Promise<{ success: boolean; timestamp?: number; error?: string }> {
    try {
      // Format: data:<MIME-TYPE>;filename=<FILENAME>;base64,<BASE64 ENCODED DATA>
      const base64Attachment = `data:${mimeType};filename=${filename};base64,${attachment}`;

      const response = await this.client.post(`/v2/send`, {
        number: fromNumber,
        recipients: [toNumber],
        message: message || '',
        base64_attachments: [base64Attachment],
      });

      this.logger.log(`Message with attachment sent to ${toNumber}: ${filename}`);
      return {
        success: true,
        timestamp: response.data.timestamp,
      };
    } catch (error: any) {
      this.logger.error(`Failed to send attachment to ${toNumber}:`, error.message);
      return {
        success: false,
        error: error.response?.data?.error || error.message,
      };
    }
  }
}
