/**
 * TextMe REST API Client
 * Base URL: https://api.textme-app.com
 */

const BASE_URL = 'https://api.textme-app.com';

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

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

// ============================================================================
// Request/Response Interfaces - Authentication
// ============================================================================

export interface AuthTokenRequest {
  email: string;
  password: string;
}

export interface AuthTokenResponse {
  access: string;
  refresh: string;
  user_id?: number;
}

export interface AuthTokenRefreshRequest {
  refresh: string;
}

export interface AuthTokenRefreshResponse {
  access: string;
  refresh?: string;
}

export interface RegisterDeviceRequest {
  device_token: string;
  device_type: 'ios' | 'android' | 'web';
  device_id?: string;
  app_version?: string;
}

export interface RegisterDeviceResponse {
  id: number;
  device_token: string;
  device_type: string;
  created_at: string;
}

// ============================================================================
// Request/Response Interfaces - User
// ============================================================================

export interface UserInfo {
  id: number;
  email: string;
  username?: string;
  first_name?: string;
  last_name?: string;
  phone_number?: string;
  profile_picture?: string;
  is_verified: boolean;
  created_at: string;
  updated_at: string;
}

export interface UserSettings {
  notifications_enabled: boolean;
  sound_enabled: boolean;
  vibration_enabled: boolean;
  read_receipts: boolean;
  typing_indicators: boolean;
  auto_download_media: boolean;
  theme?: 'light' | 'dark' | 'system';
  language?: string;
  timezone?: string;
}

export interface UpdateUserSettingsRequest extends Partial<UserSettings> {}

export interface ProfilePictureRequest {
  file: Blob | File;
}

export interface ProfilePictureResponse {
  url: string;
  thumbnail_url?: string;
}

// ============================================================================
// Request/Response Interfaces - Phone Numbers
// ============================================================================

export interface AvailableCountry {
  code: string;
  name: string;
  dial_code: string;
  flag_emoji?: string;
  available_numbers_count?: number;
}

export interface AvailableCountriesResponse {
  countries: AvailableCountry[];
}

export interface ChoosePhoneNumberRequest {
  country_code: string;
  area_code?: string;
  number?: string; // specific number if available
}

export interface PhoneNumber {
  id: number;
  number: string;
  country_code: string;
  is_primary: boolean;
  is_verified: boolean;
  capabilities: {
    sms: boolean;
    mms: boolean;
    voice: boolean;
  };
  created_at: string;
}

export interface ChoosePhoneNumberResponse {
  phone_number: PhoneNumber;
}

// ============================================================================
// Request/Response Interfaces - Messaging
// ============================================================================

export interface Participant {
  id: number;
  phone_number: string;
  name?: string;
  avatar_url?: string;
}

export interface Message {
  id: number;
  conversation_id: number;
  sender_id: number;
  body: string;
  type: 'text' | 'image' | 'video' | 'audio' | 'file' | 'location';
  status: 'pending' | 'sent' | 'delivered' | 'read' | 'failed';
  attachments?: Attachment[];
  created_at: string;
  updated_at: string;
  read_at?: string;
}

export interface Conversation {
  id: number;
  type: 'direct' | 'group';
  name?: string;
  participants: Participant[];
  last_message?: Message;
  unread_count: number;
  is_muted: boolean;
  is_archived: boolean;
  created_at: string;
  updated_at: string;
}

export interface ListConversationsParams {
  limit?: number;
  offset?: number;
  archived?: boolean;
}

export interface ListConversationsResponse {
  conversations: Conversation[];
  count: number;
  next?: string;
  previous?: string;
}

export interface SendMessageRequest {
  body: string;
  type?: 'text' | 'image' | 'video' | 'audio' | 'file' | 'location';
  attachment_ids?: number[];
  reply_to_id?: number;
  metadata?: Record<string, unknown>;
}

export interface SendMessageResponse {
  message: Message;
}

// ============================================================================
// Request/Response Interfaces - Attachments
// ============================================================================

export interface Attachment {
  id: number;
  url: string;
  thumbnail_url?: string;
  filename: string;
  content_type: string;
  size: number;
  duration?: number; // for audio/video
  width?: number;
  height?: number;
  created_at: string;
}

export interface GetUploadUrlRequest {
  filename: string;
  content_type: string;
  size: number;
}

export interface GetUploadUrlResponse {
  upload_url: string;
  attachment_id: number;
  expires_at: string;
  fields?: Record<string, string>; // for S3-style signed uploads
}

export interface CreateAttachmentRequest {
  file: Blob | File;
  conversation_id?: number;
}

export interface CreateAttachmentResponse {
  attachment: Attachment;
}

// ============================================================================
// Request/Response Interfaces - Groups
// ============================================================================

export interface Group {
  id: number;
  name: string;
  description?: string;
  avatar_url?: string;
  participants: Participant[];
  admin_ids: number[];
  conversation_id: number;
  created_at: string;
  updated_at: string;
}

export interface ListGroupsParams {
  limit?: number;
  offset?: number;
}

export interface ListGroupsResponse {
  groups: Group[];
  count: number;
  next?: string;
  previous?: string;
}

export interface CreateGroupRequest {
  name: string;
  description?: string;
  participant_phone_numbers: string[];
  avatar?: Blob | File;
}

export interface CreateGroupResponse {
  group: Group;
}

// ============================================================================
// Request/Response Interfaces - Calls
// ============================================================================

export interface InitiateCallRequest {
  to_phone_number: string;
  from_phone_number_id?: number;
  type?: 'voice' | 'video';
}

export interface Call {
  id: number;
  call_sid: string;
  from_number: string;
  to_number: string;
  type: 'voice' | 'video';
  status: 'initiated' | 'ringing' | 'in-progress' | 'completed' | 'failed' | 'busy' | 'no-answer';
  direction: 'outbound' | 'inbound';
  duration?: number;
  started_at?: string;
  ended_at?: string;
  created_at: string;
}

export interface InitiateCallResponse {
  call: Call;
  token?: string; // WebRTC/Twilio token for client-side connection
}

// ============================================================================
// Auth Headers Type
// ============================================================================

export interface AuthHeaders {
  Authorization: string;
  [key: string]: string;
}

// ============================================================================
// TextMe API Client
// ============================================================================

export class TextMeAPI {
  private baseUrl: string;
  private headers: Record<string, string>;

  constructor(authHeaders: AuthHeaders, baseUrl: string = BASE_URL) {
    this.baseUrl = baseUrl;
    this.headers = {
      'Content-Type': 'application/json',
      ...authHeaders,
    };
  }

  /**
   * Update authorization headers (e.g., after token refresh)
   */
  setAuthHeaders(authHeaders: AuthHeaders): void {
    this.headers = {
      ...this.headers,
      ...authHeaders,
    };
  }

  /**
   * Generic request handler with error handling
   */
  private async request<T>(
    method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',
    endpoint: string,
    body?: unknown,
    customHeaders?: Record<string, string>
  ): Promise<T> {
    const url = `${this.baseUrl}${endpoint}`;
    const headers = { ...this.headers, ...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 {
        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 TextMeAPIError(
        response.status,
        response.statusText,
        errorBody,
        endpoint
      );
    }

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

    return response.json() as Promise<T>;
  }

  // ==========================================================================
  // Authentication Endpoints
  // ==========================================================================

  /**
   * Login with email and password
   * Note: This is typically called without auth headers
   */
  static async login(
    credentials: AuthTokenRequest,
    baseUrl: string = BASE_URL
  ): Promise<AuthTokenResponse> {
    const response = await fetch(`${baseUrl}/api/auth-token/`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(credentials),
    });

    if (!response.ok) {
      const errorBody = await response.json().catch(() => response.text());
      throw new TextMeAPIError(
        response.status,
        response.statusText,
        errorBody,
        '/api/auth-token/'
      );
    }

    return response.json() as Promise<AuthTokenResponse>;
  }

  /**
   * Refresh access token
   * Note: This can be called without auth headers
   */
  static async refreshToken(
    request: AuthTokenRefreshRequest,
    baseUrl: string = BASE_URL
  ): Promise<AuthTokenRefreshResponse> {
    const response = await fetch(`${baseUrl}/api/auth-token-refresh/`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(request),
    });

    if (!response.ok) {
      const errorBody = await response.json().catch(() => response.text());
      throw new TextMeAPIError(
        response.status,
        response.statusText,
        errorBody,
        '/api/auth-token-refresh/'
      );
    }

    return response.json() as Promise<AuthTokenRefreshResponse>;
  }

  /**
   * Register a device for push notifications
   */
  async registerDevice(
    request: RegisterDeviceRequest
  ): Promise<RegisterDeviceResponse> {
    return this.request<RegisterDeviceResponse>(
      'POST',
      '/api/register-device/',
      request
    );
  }

  // ==========================================================================
  // User Endpoints
  // ==========================================================================

  /**
   * Get current user information
   */
  async getUserInfo(): Promise<UserInfo> {
    return this.request<UserInfo>('GET', '/api/user-info/');
  }

  /**
   * Get user settings
   */
  async getSettings(): Promise<UserSettings> {
    return this.request<UserSettings>('GET', '/api/settings/');
  }

  /**
   * Update user settings
   */
  async updateSettings(
    settings: UpdateUserSettingsRequest
  ): Promise<UserSettings> {
    return this.request<UserSettings>('PUT', '/api/settings/', settings);
  }

  /**
   * Upload profile picture
   */
  async uploadProfilePicture(file: Blob | File): Promise<ProfilePictureResponse> {
    const formData = new FormData();
    formData.append('file', file);
    return this.request<ProfilePictureResponse>(
      'POST',
      '/api/profile-picture/',
      formData
    );
  }

  // ==========================================================================
  // Phone Number Endpoints
  // ==========================================================================

  /**
   * Get available countries for phone numbers
   */
  async getAvailableCountries(): Promise<AvailableCountriesResponse> {
    return this.request<AvailableCountriesResponse>(
      'GET',
      '/api/phone-number/available-countries/'
    );
  }

  /**
   * Choose/claim a phone number
   */
  async choosePhoneNumber(
    request: ChoosePhoneNumberRequest
  ): Promise<ChoosePhoneNumberResponse> {
    return this.request<ChoosePhoneNumberResponse>(
      'POST',
      '/api/phone-number/choose/',
      request
    );
  }

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

  /**
   * List conversations
   */
  async listConversations(
    params?: ListConversationsParams
  ): Promise<ListConversationsResponse> {
    const queryParams = new URLSearchParams();
    if (params?.limit) queryParams.set('limit', params.limit.toString());
    if (params?.offset) queryParams.set('offset', params.offset.toString());
    if (params?.archived !== undefined)
      queryParams.set('archived', params.archived.toString());

    const query = queryParams.toString();
    const endpoint = `/api/conversation/${query ? `?${query}` : ''}`;
    return this.request<ListConversationsResponse>('GET', endpoint);
  }

  /**
   * Get a specific conversation
   */
  async getConversation(conversationId: number): Promise<Conversation> {
    return this.request<Conversation>(
      'GET',
      `/api/conversation/${conversationId}/`
    );
  }

  /**
   * Send a message to a conversation
   */
  async sendMessage(
    conversationId: number,
    request: SendMessageRequest
  ): Promise<SendMessageResponse> {
    return this.request<SendMessageResponse>(
      'POST',
      `/api/conversation/${conversationId}/message/`,
      request
    );
  }

  /**
   * Get messages in a conversation
   */
  async getMessages(
    conversationId: number,
    params?: { limit?: number; before?: number; after?: number }
  ): Promise<{ messages: Message[]; count: number }> {
    const queryParams = new URLSearchParams();
    if (params?.limit) queryParams.set('limit', params.limit.toString());
    if (params?.before) queryParams.set('before', params.before.toString());
    if (params?.after) queryParams.set('after', params.after.toString());

    const query = queryParams.toString();
    const endpoint = `/api/conversation/${conversationId}/message/${query ? `?${query}` : ''}`;
    return this.request<{ messages: Message[]; count: number }>('GET', endpoint);
  }

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

  /**
   * Get a presigned upload URL for an attachment
   */
  async getAttachmentUploadUrl(
    request: GetUploadUrlRequest
  ): Promise<GetUploadUrlResponse> {
    return this.request<GetUploadUrlResponse>(
      'POST',
      '/api/attachment/get-upload-url/',
      request
    );
  }

  /**
   * Create/upload an attachment directly
   */
  async createAttachment(
    file: Blob | File,
    conversationId?: number
  ): Promise<CreateAttachmentResponse> {
    const formData = new FormData();
    formData.append('file', file);
    if (conversationId) {
      formData.append('conversation_id', conversationId.toString());
    }
    return this.request<CreateAttachmentResponse>(
      'POST',
      '/api/attachment/',
      formData
    );
  }

  /**
   * Upload file to presigned URL (helper method)
   */
  async uploadToPresignedUrl(
    uploadUrl: string,
    file: Blob | File,
    fields?: Record<string, string>
  ): Promise<void> {
    const formData = new FormData();
    
    // Add any required fields (for S3-style uploads)
    if (fields) {
      Object.entries(fields).forEach(([key, value]) => {
        formData.append(key, value);
      });
    }
    
    formData.append('file', file);

    const response = await fetch(uploadUrl, {
      method: 'POST',
      body: formData,
    });

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

  // ==========================================================================
  // Group Endpoints
  // ==========================================================================

  /**
   * List groups
   */
  async listGroups(params?: ListGroupsParams): Promise<ListGroupsResponse> {
    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/group/${query ? `?${query}` : ''}`;
    return this.request<ListGroupsResponse>('GET', endpoint);
  }

  /**
   * Get a specific group
   */
  async getGroup(groupId: number): Promise<Group> {
    return this.request<Group>('GET', `/api/group/${groupId}/`);
  }

  /**
   * Create a new group
   */
  async createGroup(request: CreateGroupRequest): Promise<CreateGroupResponse> {
    if (request.avatar) {
      const formData = new FormData();
      formData.append('name', request.name);
      if (request.description) {
        formData.append('description', request.description);
      }
      request.participant_phone_numbers.forEach((phone, index) => {
        formData.append(`participant_phone_numbers[${index}]`, phone);
      });
      formData.append('avatar', request.avatar);
      return this.request<CreateGroupResponse>('POST', '/api/group/', formData);
    }

    return this.request<CreateGroupResponse>('POST', '/api/group/', {
      name: request.name,
      description: request.description,
      participant_phone_numbers: request.participant_phone_numbers,
    });
  }

  /**
   * Update a group
   */
  async updateGroup(
    groupId: number,
    updates: Partial<Pick<Group, 'name' | 'description'>>
  ): Promise<Group> {
    return this.request<Group>('PUT', `/api/group/${groupId}/`, updates);
  }

  /**
   * Add participant to group
   */
  async addGroupParticipant(
    groupId: number,
    phoneNumber: string
  ): Promise<Group> {
    return this.request<Group>(
      'POST',
      `/api/group/${groupId}/participants/`,
      { phone_number: phoneNumber }
    );
  }

  /**
   * Remove participant from group
   */
  async removeGroupParticipant(
    groupId: number,
    participantId: number
  ): Promise<void> {
    return this.request<void>(
      'DELETE',
      `/api/group/${groupId}/participants/${participantId}/`
    );
  }

  // ==========================================================================
  // Call Endpoints
  // ==========================================================================

  /**
   * Initiate a call
   */
  async initiateCall(request: InitiateCallRequest): Promise<InitiateCallResponse> {
    return this.request<InitiateCallResponse>('POST', '/api/call/', request);
  }

  /**
   * Get call details
   */
  async getCall(callId: number): Promise<Call> {
    return this.request<Call>('GET', `/api/call/${callId}/`);
  }

  /**
   * End a call
   */
  async endCall(callId: number): Promise<Call> {
    return this.request<Call>('POST', `/api/call/${callId}/end/`);
  }
}

// ============================================================================
// Factory function for convenience
// ============================================================================

/**
 * Create an authenticated TextMeAPI client
 */
export function createTextMeClient(
  accessToken: string,
  baseUrl?: string
): TextMeAPI {
  return new TextMeAPI(
    { Authorization: `Bearer ${accessToken}` },
    baseUrl
  );
}

/**
 * Create a TextMeAPI client from login credentials
 */
export async function createTextMeClientFromCredentials(
  email: string,
  password: string,
  baseUrl?: string
): Promise<{ client: TextMeAPI; tokens: AuthTokenResponse }> {
  const tokens = await TextMeAPI.login({ email, password }, baseUrl);
  const client = new TextMeAPI(
    { Authorization: `Bearer ${tokens.access}` },
    baseUrl
  );
  return { client, tokens };
}

export default TextMeAPI;
