/**
 * TextNow REST API Client
 * Base URL: https://www.textnow.com
 */

import { readFileSync } from 'node:fs';
import { TextNowAuth } from './auth.js';

const BASE_URL = 'https://www.textnow.com';

// ============================================================================
// Error Types
// ============================================================================

export class TextNowAPIError extends Error {
  constructor(
    public statusCode: number,
    public statusText: string,
    public body: unknown,
    public endpoint: string
  ) {
    super(`TextNow API Error [${statusCode}] ${statusText} at ${endpoint}`);
    this.name = 'TextNowAPIError';
  }
}

// ============================================================================
// Types
// ============================================================================

export interface Message {
  id: string;
  message_direction: 1 | 2; // 1 = incoming, 2 = outgoing
  message_type: number;
  message: string;
  contact_value: string;
  contact_type: number;
  read: boolean;
  date: string;
  conversation_id: string;
  media_type?: number;
  message_media_url?: string;
}

export interface Conversation {
  contact_value: string;
  contact_name?: string;
  contact_type: number;
  messages: Message[];
  unread_count: number;
  last_message_time: string;
}

export interface SendMessageRequest {
  contact_value: string;
  message: string;
}

export interface SendMessageResponse {
  id: string;
  message_direction: number;
  message: string;
  date: string;
}

export interface SendMediaRequest {
  contact_value: string;
  filePath?: string;
  buffer?: Buffer;
  filename?: string;
  contentType?: string;
  mediaType?: 'image' | 'audio';
}

export interface AttachmentUrlResponse {
  result: string; // The presigned upload URL
}

// ============================================================================
// TextNow API Client
// ============================================================================

export class TextNowAPI {
  private baseUrl: string;
  private auth: TextNowAuth;

  constructor(auth: TextNowAuth, baseUrl: string = BASE_URL) {
    this.baseUrl = baseUrl;
    this.auth = auth;
  }

  /**
   * Get default headers for all requests
   */
  private getHeaders(contentType: string = 'application/json'): Record<string, string> {
    const authHeaders = this.auth.getAuthHeaders();
    return {
      ...authHeaders,
      'Content-Type': contentType,
    };
  }

  /**
   * Generic request handler with error handling
   */
  private async request<T>(
    method: 'GET' | 'POST' | 'PUT' | 'DELETE',
    endpoint: string,
    body?: unknown,
    customHeaders?: Record<string, string>
  ): Promise<T> {
    const url = `${this.baseUrl}${endpoint}`;
    const headers = { ...this.getHeaders(), ...customHeaders };

    const options: RequestInit = {
      method,
      headers,
    };

    if (body !== undefined && method !== 'GET') {
      if (body instanceof FormData) {
        // Remove Content-Type for FormData (browser sets it with boundary)
        delete (options.headers as Record<string, string>)['Content-Type'];
        options.body = body;
      } else if (Buffer.isBuffer(body)) {
        options.body = body;
      } else {
        options.body = JSON.stringify(body);
      }
    }

    const response = await fetch(url, options);

    if (!response.ok) {
      let errorBody: unknown;
      try {
        errorBody = await response.json();
      } catch {
        errorBody = await response.text();
      }
      throw new TextNowAPIError(
        response.status,
        response.statusText,
        errorBody,
        endpoint
      );
    }

    // Handle empty responses
    if (response.status === 204) {
      return {} as T;
    }

    const text = await response.text();
    if (!text) {
      return {} as T;
    }

    try {
      return JSON.parse(text) as T;
    } catch {
      return text as unknown as T;
    }
  }

  // ==========================================================================
  // Messaging Endpoints
  // ==========================================================================

  /**
   * Send an SMS message
   * POST /api/users/{username}/messages
   */
  async send(to: string, message: string): Promise<SendMessageResponse> {
    const username = this.auth.getUsername();
    const endpoint = `/api/users/${username}/messages`;
    
    const payload = {
      contact_value: to,
      message_direction: 2, // Outgoing
      contact_type: 2, // Phone number
      message: message,
    };

    return this.request<SendMessageResponse>('POST', endpoint, payload);
  }

  /**
   * Get messages/conversations
   * GET /api/users/{username}/messages
   */
  async getMessages(params?: {
    limit?: number;
    start_message_id?: string;
    direction?: 'past' | 'future';
    contact_value?: string;
  }): Promise<{ messages: Message[] }> {
    const username = this.auth.getUsername();
    
    const queryParams = new URLSearchParams();
    if (params?.limit) queryParams.set('limit', params.limit.toString());
    if (params?.start_message_id) queryParams.set('start_message_id', params.start_message_id);
    if (params?.direction) queryParams.set('direction', params.direction);
    if (params?.contact_value) queryParams.set('contact_value', params.contact_value);
    
    const query = queryParams.toString();
    const endpoint = `/api/users/${username}/messages${query ? `?${query}` : ''}`;
    
    return this.request<{ messages: Message[] }>('GET', endpoint);
  }

  /**
   * Get conversations list
   * GET /api/users/{username}/conversations
   */
  async getConversations(params?: {
    limit?: number;
    offset?: number;
  }): Promise<{ conversations: Conversation[] }> {
    const username = this.auth.getUsername();
    
    const queryParams = new URLSearchParams();
    if (params?.limit) queryParams.set('limit', params.limit.toString());
    if (params?.offset) queryParams.set('offset', params.offset.toString());
    
    const query = queryParams.toString();
    const endpoint = `/api/users/${username}/conversations${query ? `?${query}` : ''}`;
    
    return this.request<{ conversations: Conversation[] }>('GET', endpoint);
  }

  // ==========================================================================
  // Media/Attachment Endpoints
  // ==========================================================================

  /**
   * Get a presigned URL for uploading media
   * GET /api/v3/attachment_url?message_type=2 (images) or message_type=3 (audio)
   */
  async getUploadUrl(mediaType: 'image' | 'audio' = 'image'): Promise<string> {
    const messageType = mediaType === 'image' ? 2 : 3;
    const endpoint = `/api/v3/attachment_url?message_type=${messageType}`;
    
    const response = await this.request<AttachmentUrlResponse>('GET', endpoint);
    return response.result;
  }

  /**
   * Upload a file to the presigned URL
   * PUT to presigned URL with raw binary
   */
  async uploadToPresignedUrl(
    uploadUrl: string,
    data: Buffer,
    contentType: string
  ): Promise<void> {
    const response = await fetch(uploadUrl, {
      method: 'PUT',
      headers: {
        'Content-Type': contentType,
      },
      body: data,
    });

    if (!response.ok) {
      throw new TextNowAPIError(
        response.status,
        response.statusText,
        await response.text(),
        uploadUrl
      );
    }
  }

  /**
   * Send media attachment
   * POST /api/v3/send_attachment with form data
   */
  async sendMedia(options: SendMediaRequest): Promise<SendMessageResponse> {
    const { contact_value, filePath, buffer, filename, contentType, mediaType = 'image' } = options;
    
    // Get file data
    let fileData: Buffer;
    let fileContentType = contentType || 'image/jpeg';
    let fileName = filename || 'attachment.jpg';
    
    if (buffer) {
      fileData = buffer;
    } else if (filePath) {
      fileData = readFileSync(filePath);
      // Infer content type from extension if not provided
      if (!contentType) {
        const ext = filePath.split('.').pop()?.toLowerCase();
        const mimeTypes: Record<string, string> = {
          'jpg': 'image/jpeg',
          'jpeg': 'image/jpeg',
          'png': 'image/png',
          'gif': 'image/gif',
          'webp': 'image/webp',
          'mp3': 'audio/mpeg',
          'wav': 'audio/wav',
          'm4a': 'audio/mp4',
        };
        fileContentType = mimeTypes[ext || ''] || 'application/octet-stream';
      }
      fileName = filePath.split('/').pop() || fileName;
    } else {
      throw new TextNowAPIError(400, 'Bad Request', 'Must provide filePath or buffer', '/api/v3/send_attachment');
    }

    // Step 1: Get presigned upload URL
    const uploadUrl = await this.getUploadUrl(mediaType);

    // Step 2: Upload the file
    await this.uploadToPresignedUrl(uploadUrl, fileData, fileContentType);

    // Step 3: Send the attachment message
    // Extract the file key/path from the upload URL
    const url = new URL(uploadUrl);
    const mediaUrl = `${url.origin}${url.pathname}`;

    const formData = new FormData();
    formData.append('contact_value', contact_value);
    formData.append('contact_type', '2'); // Phone number
    formData.append('message_direction', '2'); // Outgoing
    formData.append('message_type', mediaType === 'image' ? '2' : '3');
    formData.append('media_url', mediaUrl);
    formData.append('filename', fileName);

    const endpoint = '/api/v3/send_attachment';
    return this.request<SendMessageResponse>('POST', endpoint, formData);
  }

  // ==========================================================================
  // Convenience Methods
  // ==========================================================================

  /**
   * Send an image file
   */
  async sendImage(to: string, filePath: string): Promise<SendMessageResponse> {
    return this.sendMedia({
      contact_value: to,
      filePath,
      mediaType: 'image',
    });
  }

  /**
   * Send an image from buffer
   */
  async sendImageBuffer(
    to: string,
    buffer: Buffer,
    filename: string = 'image.jpg',
    contentType: string = 'image/jpeg'
  ): Promise<SendMessageResponse> {
    return this.sendMedia({
      contact_value: to,
      buffer,
      filename,
      contentType,
      mediaType: 'image',
    });
  }

  /**
   * Send an audio file
   */
  async sendAudio(to: string, filePath: string): Promise<SendMessageResponse> {
    return this.sendMedia({
      contact_value: to,
      filePath,
      mediaType: 'audio',
    });
  }

  /**
   * Get messages from a specific contact
   */
  async getMessagesFrom(
    contactNumber: string,
    limit: number = 50
  ): Promise<Message[]> {
    const result = await this.getMessages({
      contact_value: contactNumber,
      limit,
    });
    return result.messages || [];
  }

  /**
   * Mark messages as read (if endpoint exists)
   * This is a placeholder - actual endpoint may vary
   */
  async markAsRead(messageIds: string[]): Promise<void> {
    const username = this.auth.getUsername();
    const endpoint = `/api/users/${username}/messages/read`;
    
    await this.request<void>('POST', endpoint, {
      message_ids: messageIds,
    });
  }
}

// ============================================================================
// Factory Functions
// ============================================================================

/**
 * Create a TextNowAPI client from stored credentials
 */
export function createTextNowClient(): TextNowAPI {
  const auth = new TextNowAuth();
  
  if (!auth.isAuthenticated()) {
    throw new TextNowAPIError(
      401,
      'Not Authenticated',
      'No stored credentials found. Please set credentials first.',
      'initialization'
    );
  }
  
  return new TextNowAPI(auth);
}

/**
 * Create a TextNowAPI client with explicit credentials
 */
export function createTextNowClientWithCredentials(
  username: string,
  cookies: string,
  save: boolean = true
): TextNowAPI {
  const auth = new TextNowAuth();
  auth.setCredentials(username, cookies, save);
  return new TextNowAPI(auth);
}

export default TextNowAPI;
