/**
 * Binect API client
 *
 * This module wraps the @binect/js library to provide a simplified API
 * for the Chrome extension. It delegates all API integration to the
 * upstream library.
 */

import {
  BinectClient,
  BinectApiError,
  BinectAuthError,
  type Document as BinectDocument,
  type DocumentUploadOptions,
  EnvelopeType,
  FrankingType,
} from '@binect/js';

// Re-export types for backward compatibility
export interface Options {
  simplex?: boolean;  // if false, it's duplex
  color?: boolean;    // if false, it's black and white
  envelope?: 'DINLANG' | 'C4';
  franking?: 'UNSPECIFIED' | 'STANDARD_FRANKING' | 'DV_FRANKING';
}

// Document type matching what popup.ts expects
export interface Document {
  id: number;
  filename: string;
  numberOfPages?: number;
  status: {
    code: number;
    text: string;
  };
  documentType: 'Letter' | 'SerialLetter';
  letter?: {
    letterType: 'LetterData' | 'Error';
    letterData?: {
      recipientAddress: string;
      price: {
        priceBeforeTax: number;
        priceAfterTax: number;
        unit: string;
        taxInPercent: number;
      };
      international: boolean;
      options: Options;
    };
    errors?: Array<{
      code: number;
      text: string;
      blankText: string;
    }>;
  };
}

/**
 * Custom error class for Binect API errors
 * Wraps errors from @binect/js for backward compatibility
 */
export class BinectAPIError extends Error {
  constructor(
    message: string,
    public statusCode?: number,
    public response?: unknown
  ) {
    super(message);
    this.name = 'BinectAPIError';
  }

  /**
   * Create from a @binect/js error
   */
  static fromBinectError(error: BinectApiError | BinectAuthError): BinectAPIError {
    if (error instanceof BinectAuthError) {
      return new BinectAPIError('Invalid credentials', 401);
    }
    return new BinectAPIError(
      error.message,
      error.status,
      error.response
    );
  }
}

/**
 * Convert ArrayBuffer to base64 string (browser-compatible)
 */
function arrayBufferToBase64(buffer: ArrayBuffer): string {
  const bytes = new Uint8Array(buffer);
  let binary = '';
  for (let i = 0; i < bytes.byteLength; i++) {
    binary += String.fromCharCode(bytes[i]);
  }
  return btoa(binary);
}

/**
 * Map local Options to DocumentUploadOptions
 */
function mapOptions(options?: Options): Partial<DocumentUploadOptions> {
  if (!options) {
    return {
      simplex: false,  // duplex by default
      color: false,    // black and white by default
    };
  }

  const mapped: Partial<DocumentUploadOptions> = {
    simplex: options.simplex ?? false,
    color: options.color ?? false,
  };

  if (options.envelope) {
    mapped.envelope = options.envelope === 'C4' ? EnvelopeType.C4 : EnvelopeType.DINLANG;
  }

  if (options.franking) {
    switch (options.franking) {
      case 'STANDARD_FRANKING':
        mapped.franking = FrankingType.STANDARD_FRANKING;
        break;
      case 'DV_FRANKING':
        mapped.franking = FrankingType.DV_FRANKING;
        break;
      default:
        mapped.franking = FrankingType.UNSPECIFIED;
    }
  }

  return mapped;
}

/**
 * Convert BinectDocument to our Document interface
 */
function mapDocument(doc: BinectDocument): Document {
  // Build letter data if present
  let letterData: Document['letter'] = undefined;

  if (doc.letter) {
    const ld = doc.letter.letterData;
    letterData = {
      letterType: (doc.letter.letterType || 'LetterData') as 'LetterData' | 'Error',
      letterData: ld ? {
        recipientAddress: ld.recipientAddress || '',
        price: ld.price || { priceBeforeTax: 0, priceAfterTax: 0, unit: 'EUROCENT', taxInPercent: 0 },
        international: ld.international || false,
        options: {
          simplex: ld.options?.simplex,
          color: ld.options?.color,
          envelope: ld.options?.envelope as 'DINLANG' | 'C4' | undefined,
          franking: ld.options?.franking as 'UNSPECIFIED' | 'STANDARD_FRANKING' | 'DV_FRANKING' | undefined,
        },
      } : undefined,
      errors: doc.letter.errors?.map(e => ({
        code: parseInt(e.code, 10) || 0,
        text: e.message,
        blankText: e.message,
      })),
    };
  }

  return {
    id: doc.id,
    filename: doc.filename || 'document.pdf',
    numberOfPages: doc.numberOfPages,
    status: {
      code: doc.status.code,
      text: doc.status.text,
    },
    documentType: (doc.documentType === 'SERIALLETTER' ? 'SerialLetter' : 'Letter') as 'Letter' | 'SerialLetter',
    letter: letterData,
  };
}

/**
 * Upload PDF to Binect API
 *
 * Uses the @binect/js library to upload the PDF.
 *
 * @param pdfData - PDF file as ArrayBuffer
 * @param filename - Name of the PDF file
 * @param username - Binect username for authentication
 * @param password - Binect password for authentication
 * @param options - Optional printing options (simplex/duplex, color/bw, etc.)
 * @returns Document object with ID and status
 */
export async function uploadPDF(
  pdfData: ArrayBuffer,
  filename: string,
  username: string,
  password: string,
  options?: Options
): Promise<Document> {
  console.log('[Binect API] Uploading PDF to Binect via @binect/js...');
  console.log('[Binect API] Filename:', filename);
  console.log('[Binect API] PDF size:', pdfData.byteLength, 'bytes');

  try {
    // Create client with credentials
    const client = new BinectClient({
      username,
      password,
    });

    // Convert PDF to base64
    console.log('[Binect API] Converting PDF to base64...');
    const base64Content = arrayBufferToBase64(pdfData);
    console.log('[Binect API] Base64 length:', base64Content.length, 'characters');

    // Map options
    const uploadOptions = mapOptions(options);
    console.log('[Binect API] Upload options:', uploadOptions);

    // Upload document
    const doc = await client.documents.upload({
      content: base64Content,
      filename,
      ...uploadOptions,
    });

    console.log('[Binect API] Upload successful!');
    console.log('[Binect API] Document ID:', doc.id);
    console.log('[Binect API] Document status:', doc.status);

    // Check if document has errors
    if (doc.letter?.letterType === 'Error' && doc.letter.errors) {
      console.warn('[Binect API] Document has errors:', doc.letter.errors);
      const errorMessages = doc.letter.errors.map(e => e.message).join('; ');
      throw new BinectAPIError(
        `Document validation failed: ${errorMessages}`,
        200,
        doc
      );
    }

    // Status code 7 = erroneous
    if (doc.status.code === 7) {
      console.error('[Binect API] Document is erroneous:', doc.status.text);
      throw new BinectAPIError(
        `Document is erroneous: ${doc.status.text}`,
        200,
        doc
      );
    }

    return mapDocument(doc);
  } catch (error) {
    console.error('[Binect API] Upload error:', error);

    // Handle @binect/js errors
    if (error instanceof BinectAuthError) {
      throw new BinectAPIError('Invalid credentials', 401);
    }

    if (error instanceof BinectApiError) {
      // Map specific status codes
      if (error.status === 400) {
        throw new BinectAPIError(
          'Invalid request. Please check the PDF format and size.',
          400
        );
      }
      if (error.status === 413) {
        throw new BinectAPIError('File size exceeds limit (12 MB)', 413);
      }
      throw BinectAPIError.fromBinectError(error);
    }

    // Already a BinectAPIError
    if (error instanceof BinectAPIError) {
      throw error;
    }

    // Network or other errors
    if (error instanceof TypeError && error.message.includes('fetch')) {
      throw new BinectAPIError(
        'Cannot reach Binect API. Please check your internet connection.'
      );
    }

    throw new BinectAPIError(
      `Network error: ${error instanceof Error ? error.message : 'Unknown error'}`
    );
  }
}

/**
 * Test API connectivity by fetching account information
 *
 * @param username - Binect username
 * @param password - Binect password
 * @returns true if authentication successful, false otherwise
 */
export async function testConnection(username: string, password: string): Promise<boolean> {
  console.log('[Binect API] Testing connection via @binect/js...');

  try {
    const client = new BinectClient({
      username,
      password,
    });

    // Attempt to get account info
    await client.accounts.get();

    console.log('[Binect API] Connection successful');
    return true;
  } catch (error) {
    if (error instanceof BinectAuthError) {
      console.log('[Binect API] Authentication failed');
      return false;
    }

    if (error instanceof BinectApiError) {
      console.warn('[Binect API] API error:', error.message);
      // If we get any other API error, credentials might still be valid
      // but there's another issue
      return false;
    }

    console.error('[Binect API] Connection test error:', error);
    return false;
  }
}

/**
 * Document status information returned by getDocumentStatus
 */
export interface DocumentStatusInfo {
  status: number;
  statusText: string;
  price?: number;
  recipientAddress?: string;
}

/**
 * Ship a document (place order for production)
 *
 * This announces the document for delivery, transitioning it from
 * SHIPPABLE to PRODUCTION_QUEUE.
 *
 * @param documentId - Binect document ID
 * @param username - Binect username
 * @param password - Binect password
 * @returns Updated status info
 */
export async function shipDocument(
  documentId: number,
  username: string,
  password: string
): Promise<DocumentStatusInfo> {
  console.log('[Binect API] Shipping document:', documentId);

  try {
    const client = new BinectClient({
      username,
      password,
    });

    // Send the document for production
    const sending = await client.sendings.send(String(documentId));

    console.log('[Binect API] Document shipped successfully');
    console.log('[Binect API] New status:', sending.status);

    return {
      status: sending.status,
      statusText: getStatusText(sending.status),
      price: sending.price,
    };
  } catch (error) {
    console.error('[Binect API] Ship error:', error);

    if (error instanceof BinectAuthError) {
      throw new BinectAPIError('Invalid credentials', 401);
    }

    if (error instanceof BinectApiError) {
      // Check for insufficient balance (error code 2330)
      if (error.message.includes('2330') || error.message.includes('balance')) {
        throw new BinectAPIError('Insufficient account balance', 402);
      }
      throw BinectAPIError.fromBinectError(error);
    }

    if (error instanceof BinectAPIError) {
      throw error;
    }

    throw new BinectAPIError(
      `Failed to ship document: ${error instanceof Error ? error.message : 'Unknown error'}`
    );
  }
}

/**
 * Get current status of a document
 *
 * @param documentId - Binect document ID
 * @param username - Binect username
 * @param password - Binect password
 * @returns Current status info
 */
export async function getDocumentStatus(
  documentId: number,
  username: string,
  password: string
): Promise<DocumentStatusInfo> {
  console.log('[Binect API] Getting document status:', documentId);

  try {
    const client = new BinectClient({
      username,
      password,
    });

    // Fetch document details
    const doc = await client.documents.get(String(documentId));

    console.log('[Binect API] Document status:', doc.status);

    // Extract price and recipient if available
    let price: number | undefined;
    let recipientAddress: string | undefined;

    if (doc.letter?.letterData) {
      price = doc.letter.letterData.price?.priceAfterTax;
      recipientAddress = doc.letter.letterData.recipientAddress;
    }

    return {
      status: doc.status.code,
      statusText: doc.status.text || getStatusText(doc.status.code),
      price,
      recipientAddress,
    };
  } catch (error) {
    console.error('[Binect API] Get status error:', error);

    if (error instanceof BinectAuthError) {
      throw new BinectAPIError('Invalid credentials', 401);
    }

    if (error instanceof BinectApiError) {
      throw BinectAPIError.fromBinectError(error);
    }

    if (error instanceof BinectAPIError) {
      throw error;
    }

    throw new BinectAPIError(
      `Failed to get document status: ${error instanceof Error ? error.message : 'Unknown error'}`
    );
  }
}

/**
 * Get human-readable status text for a Binect status code
 */
function getStatusText(statusCode: number): string {
  switch (statusCode) {
    case 1: return 'In preparation';
    case 2: return 'Ready to ship';
    case 3: return 'In production queue';
    case 4: return 'Printing';
    case 5: return 'Sent';
    case 6: return 'Canceled';
    case 7: return 'Has errors';
    default: return 'Unknown status';
  }
}
