import axios, { AxiosInstance, AxiosError, AxiosRequestConfig } from 'axios';
import { StripeList, StripeError } from '../types/index.js';

const STRIPE_API_BASE = 'https://api.stripe.com/v1';
const STRIPE_API_VERSION = '2024-01-18';
const DEFAULT_TIMEOUT = 30000;
const MAX_RETRIES = 3;
const INITIAL_RETRY_DELAY = 1000;

export interface StripeClientConfig {
  apiKey: string;
  apiVersion?: string;
  timeout?: number;
  maxRetries?: number;
}

export interface PaginationParams {
  limit?: number;
  starting_after?: string;
  ending_before?: string;
}

export interface RequestOptions {
  idempotencyKey?: string;
  stripeAccount?: string;
}

/**
 * Convert object to application/x-www-form-urlencoded format
 * Handles nested objects with dot notation (e.g., metadata.key=value)
 */
function encodeFormData(data: Record<string, any>, prefix = ''): string {
  const pairs: string[] = [];
  
  for (const key in data) {
    if (!Object.prototype.hasOwnProperty.call(data, key)) continue;
    
    const value = data[key];
    const encodedKey = prefix ? `${prefix}[${key}]` : key;
    
    if (value === null || value === undefined) {
      continue;
    } else if (typeof value === 'object' && !Array.isArray(value)) {
      // Recursively handle nested objects
      pairs.push(encodeFormData(value, encodedKey));
    } else if (Array.isArray(value)) {
      // Handle arrays
      value.forEach((item, index) => {
        if (typeof item === 'object') {
          pairs.push(encodeFormData(item, `${encodedKey}[${index}]`));
        } else {
          pairs.push(`${encodeURIComponent(`${encodedKey}[]`)}=${encodeURIComponent(String(item))}`);
        }
      });
    } else {
      pairs.push(`${encodeURIComponent(encodedKey)}=${encodeURIComponent(String(value))}`);
    }
  }
  
  return pairs.filter(p => p).join('&');
}

/**
 * Calculate exponential backoff delay
 */
function getRetryDelay(attempt: number, baseDelay = INITIAL_RETRY_DELAY): number {
  return Math.min(baseDelay * Math.pow(2, attempt - 1), 10000);
}

/**
 * Check if error is retryable
 */
function isRetryableError(error: AxiosError): boolean {
  if (!error.response) return true; // Network errors are retryable
  
  const status = error.response.status;
  const shouldRetry = error.response.headers['stripe-should-retry'];
  
  // Respect Stripe-Should-Retry header if present
  if (shouldRetry !== undefined) {
    return shouldRetry === 'true';
  }
  
  // Retry on 429 (rate limit), 500s, and 409 (conflict)
  return status === 429 || status === 409 || (status >= 500 && status < 600);
}

export class StripeClient {
  private client: AxiosInstance;
  private apiKey: string;
  private maxRetries: number;

  constructor(config: StripeClientConfig) {
    this.apiKey = config.apiKey;
    this.maxRetries = config.maxRetries ?? MAX_RETRIES;

    this.client = axios.create({
      baseURL: STRIPE_API_BASE,
      timeout: config.timeout ?? DEFAULT_TIMEOUT,
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Stripe-Version': config.apiVersion ?? STRIPE_API_VERSION,
        'Content-Type': 'application/x-www-form-urlencoded',
      },
    });

    this.setupInterceptors();
  }

  private setupInterceptors(): void {
    // Request interceptor
    this.client.interceptors.request.use(
      (config) => {
        // Log request (optional - can be removed in production)
        console.log(`[Stripe API] ${config.method?.toUpperCase()} ${config.url}`);
        return config;
      },
      (error) => {
        return Promise.reject(error);
      }
    );

    // Response interceptor
    this.client.interceptors.response.use(
      (response) => {
        // Log successful response
        console.log(`[Stripe API] ${response.status} ${response.config.url}`);
        return response;
      },
      async (error: AxiosError) => {
        const config = error.config as AxiosRequestConfig & { _retryCount?: number };
        
        if (!config) {
          return Promise.reject(error);
        }

        // Initialize retry count
        config._retryCount = config._retryCount ?? 0;

        // Check if we should retry
        if (config._retryCount < this.maxRetries && isRetryableError(error)) {
          config._retryCount++;

          // Calculate delay
          let delay = getRetryDelay(config._retryCount);

          // Respect Retry-After header if present
          if (error.response?.headers['retry-after']) {
            const retryAfter = parseInt(error.response.headers['retry-after'], 10);
            delay = retryAfter * 1000;
          }

          console.log(`[Stripe API] Retrying request (${config._retryCount}/${this.maxRetries}) after ${delay}ms`);

          // Wait before retrying
          await new Promise(resolve => setTimeout(resolve, delay));

          return this.client.request(config);
        }

        // Format error
        const stripeError = this.formatError(error);
        return Promise.reject(stripeError);
      }
    );
  }

  private formatError(error: AxiosError): Error {
    if (error.response?.data) {
      const data = error.response.data as any;
      if (data.error) {
        const stripeError = data.error as StripeError;
        return new Error(`Stripe API Error (${stripeError.type}): ${stripeError.message}`);
      }
    }
    return new Error(error.message || 'Unknown Stripe API error');
  }

  /**
   * GET request
   */
  async get<T>(
    path: string,
    params?: Record<string, any>,
    options?: RequestOptions
  ): Promise<T> {
    const config: AxiosRequestConfig = {
      params,
      headers: this.buildHeaders(options),
    };

    const response = await this.client.get<T>(path, config);
    return response.data;
  }

  /**
   * POST request (form-encoded)
   */
  async post<T>(
    path: string,
    data?: Record<string, any>,
    options?: RequestOptions
  ): Promise<T> {
    const config: AxiosRequestConfig = {
      headers: this.buildHeaders(options),
    };

    const formData = data ? encodeFormData(data) : '';
    const response = await this.client.post<T>(path, formData, config);
    return response.data;
  }

  /**
   * DELETE request
   */
  async delete<T>(
    path: string,
    options?: RequestOptions
  ): Promise<T> {
    const config: AxiosRequestConfig = {
      headers: this.buildHeaders(options),
    };

    const response = await this.client.delete<T>(path, config);
    return response.data;
  }

  /**
   * Paginate through all results automatically
   */
  async *paginate<T>(
    path: string,
    params?: Record<string, any>,
    options?: RequestOptions
  ): AsyncGenerator<T, void, unknown> {
    let hasMore = true;
    let startingAfter: string | undefined;

    while (hasMore) {
      const response = await this.get<StripeList<T>>(
        path,
        {
          ...params,
          starting_after: startingAfter,
        },
        options
      );

      for (const item of response.data) {
        yield item;
      }

      hasMore = response.has_more;
      if (hasMore && response.data.length > 0) {
        // Get the ID of the last item for the next request
        const lastItem = response.data[response.data.length - 1] as any;
        startingAfter = lastItem.id;
      }
    }
  }

  /**
   * List resources with pagination support
   */
  async list<T>(
    path: string,
    params?: PaginationParams & Record<string, any>,
    options?: RequestOptions
  ): Promise<StripeList<T>> {
    return this.get<StripeList<T>>(path, params, options);
  }

  /**
   * Retrieve a single resource
   */
  async retrieve<T>(
    path: string,
    id: string,
    params?: Record<string, any>,
    options?: RequestOptions
  ): Promise<T> {
    return this.get<T>(`${path}/${id}`, params, options);
  }

  /**
   * Create a resource
   */
  async create<T>(
    path: string,
    data: Record<string, any>,
    options?: RequestOptions
  ): Promise<T> {
    return this.post<T>(path, data, options);
  }

  /**
   * Update a resource
   */
  async update<T>(
    path: string,
    id: string,
    data: Record<string, any>,
    options?: RequestOptions
  ): Promise<T> {
    return this.post<T>(`${path}/${id}`, data, options);
  }

  /**
   * Delete/cancel a resource
   */
  async remove<T>(
    path: string,
    id: string,
    options?: RequestOptions
  ): Promise<T> {
    return this.delete<T>(`${path}/${id}`, options);
  }

  /**
   * Build headers with optional idempotency key and Stripe-Account
   */
  private buildHeaders(options?: RequestOptions): Record<string, string> {
    const headers: Record<string, string> = {};

    if (options?.idempotencyKey) {
      headers['Idempotency-Key'] = options.idempotencyKey;
    }

    if (options?.stripeAccount) {
      headers['Stripe-Account'] = options.stripeAccount;
    }

    return headers;
  }

  /**
   * Generate idempotency key (UUID v4)
   */
  generateIdempotencyKey(): string {
    return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
  }
}

/**
 * Create a Stripe client instance
 */
export function createStripeClient(apiKey: string, config?: Partial<StripeClientConfig>): StripeClient {
  return new StripeClient({
    apiKey,
    ...config,
  });
}
