import type { BigCommerceConfig, BigCommerceError } from '../types/index.js';

export class BigCommerceClient {
  private storeHash: string;
  private accessToken: string;
  private apiVersion: 'v2' | 'v3';
  private baseUrl: string;
  private rateLimitRemaining: number = 20000;
  private rateLimitReset: number = Date.now() + 30000;

  constructor(config: BigCommerceConfig) {
    if (!config.storeHash || !config.accessToken) {
      throw new Error('BigCommerce storeHash and accessToken are required');
    }
    
    this.storeHash = config.storeHash;
    this.accessToken = config.accessToken;
    this.apiVersion = config.apiVersion || 'v3';
    this.baseUrl = `https://api.bigcommerce.com/stores/${this.storeHash}`;
  }

  /**
   * Make an authenticated request to the BigCommerce API
   */
  private async request<T>(
    endpoint: string,
    options: RequestInit = {},
    version?: 'v2' | 'v3'
  ): Promise<T> {
    const apiVersion = version || this.apiVersion;
    const url = `${this.baseUrl}/${apiVersion}${endpoint}`;
    
    // Rate limiting check
    await this.checkRateLimit();

    const headers: Record<string, string> = {
      'X-Auth-Token': this.accessToken,
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      ...((options.headers as Record<string, string>) || {}),
    };

    try {
      const response = await fetch(url, {
        ...options,
        headers,
      });

      // Update rate limit info from headers
      this.updateRateLimitInfo(response);

      if (!response.ok) {
        await this.handleError(response);
      }

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

      const data = await response.json();
      return data as T;
    } catch (error) {
      if (error instanceof Error) {
        throw new Error(`BigCommerce API request failed: ${error.message}`);
      }
      throw error;
    }
  }

  /**
   * Check and enforce rate limiting
   */
  private async checkRateLimit(): Promise<void> {
    const now = Date.now();
    
    // If rate limit is exhausted and reset time hasn't passed, wait
    if (this.rateLimitRemaining < 10 && now < this.rateLimitReset) {
      const waitTime = this.rateLimitReset - now;
      console.warn(`Rate limit nearly exhausted. Waiting ${waitTime}ms...`);
      await new Promise(resolve => setTimeout(resolve, waitTime));
    }
  }

  /**
   * Update rate limit tracking from response headers
   */
  private updateRateLimitInfo(response: Response): void {
    const remaining = response.headers.get('X-Rate-Limit-Requests-Left');
    const resetTime = response.headers.get('X-Rate-Limit-Time-Reset-Ms');
    
    if (remaining) {
      this.rateLimitRemaining = parseInt(remaining, 10);
    }
    
    if (resetTime) {
      this.rateLimitReset = parseInt(resetTime, 10);
    }
  }

  /**
   * Handle API errors with detailed messages
   */
  private async handleError(response: Response): Promise<never> {
    let errorMessage = `BigCommerce API error (${response.status} ${response.statusText})`;
    
    try {
      const errorData = await response.json() as BigCommerceError;
      if (errorData.title) {
        errorMessage = errorData.title;
      }
      if (errorData.errors) {
        const details = Object.entries(errorData.errors)
          .map(([key, value]) => `${key}: ${value}`)
          .join(', ');
        errorMessage += ` - ${details}`;
      }
    } catch {
      // If we can't parse error JSON, use the default message
    }

    throw new Error(errorMessage);
  }

  /**
   * GET request
   */
  async get<T>(endpoint: string, version?: 'v2' | 'v3'): Promise<T> {
    return this.request<T>(endpoint, { method: 'GET' }, version);
  }

  /**
   * POST request
   */
  async post<T>(endpoint: string, data: unknown, version?: 'v2' | 'v3'): Promise<T> {
    return this.request<T>(
      endpoint,
      {
        method: 'POST',
        body: JSON.stringify(data),
      },
      version
    );
  }

  /**
   * PUT request
   */
  async put<T>(endpoint: string, data: unknown, version?: 'v2' | 'v3'): Promise<T> {
    return this.request<T>(
      endpoint,
      {
        method: 'PUT',
        body: JSON.stringify(data),
      },
      version
    );
  }

  /**
   * DELETE request
   */
  async delete<T>(endpoint: string, version?: 'v2' | 'v3'): Promise<T> {
    return this.request<T>(endpoint, { method: 'DELETE' }, version);
  }

  /**
   * Paginated GET request - handles all pages automatically
   */
  async getPaginated<T>(
    endpoint: string,
    params: Record<string, string | number> = {},
    version?: 'v2' | 'v3'
  ): Promise<T[]> {
    const results: T[] = [];
    let page = 1;
    let hasMore = true;

    while (hasMore) {
      const queryParams = new URLSearchParams({
        ...params,
        page: page.toString(),
        limit: '250', // Max for most BigCommerce endpoints
      } as Record<string, string>);

      const url = `${endpoint}?${queryParams.toString()}`;
      const response = await this.get<{ data: T[]; meta?: { pagination?: { total_pages: number } } }>(url, version);

      if (response.data && Array.isArray(response.data)) {
        results.push(...response.data);
        
        // Check if there are more pages
        if (response.meta?.pagination?.total_pages) {
          hasMore = page < response.meta.pagination.total_pages;
        } else {
          // If no pagination meta, check if we got fewer results than the limit
          hasMore = response.data.length === 250;
        }
      } else {
        hasMore = false;
      }

      page++;
    }

    return results;
  }

  /**
   * Get a single page of results with pagination info
   */
  async getPage<T>(
    endpoint: string,
    page: number = 1,
    limit: number = 50,
    params: Record<string, string | number> = {},
    version?: 'v2' | 'v3'
  ): Promise<{ data: T[]; meta?: unknown }> {
    const queryParams = new URLSearchParams({
      ...params,
      page: page.toString(),
      limit: limit.toString(),
    } as Record<string, string>);

    const url = `${endpoint}?${queryParams.toString()}`;
    return this.get<{ data: T[]; meta?: unknown }>(url, version);
  }

  /**
   * GraphQL Storefront API request (for advanced queries)
   */
  async graphql<T>(query: string, variables?: Record<string, unknown>): Promise<T> {
    const endpoint = '/graphql';
    const response = await this.post<{ data: T }>(
      endpoint,
      { query, variables },
      'v3'
    );
    return response.data;
  }
}
