import axios, { AxiosInstance, AxiosError, AxiosRequestConfig } from 'axios';
import FormData from 'form-data';
import type { WrikeApiResponse, WrikeError, WrikeQueryParams } from '../types/index.js';

export class WrikeClient {
  private client: AxiosInstance;
  private baseURL = 'https://www.wrike.com/api/v4';
  private rateLimitRemaining = 100;
  private rateLimitReset = Date.now();

  constructor(apiToken: string) {
    if (!apiToken) {
      throw new Error('Wrike API token is required');
    }

    this.client = axios.create({
      baseURL: this.baseURL,
      headers: {
        'Authorization': `Bearer ${apiToken}`,
        'Content-Type': 'application/json',
      },
      timeout: 30000,
    });

    // Response interceptor for rate limit tracking
    this.client.interceptors.response.use(
      (response) => {
        const remaining = response.headers['x-rate-limit-remaining'];
        const reset = response.headers['x-rate-limit-reset'];
        
        if (remaining) this.rateLimitRemaining = parseInt(remaining, 10);
        if (reset) this.rateLimitReset = parseInt(reset, 10) * 1000;

        return response;
      },
      async (error) => {
        if (error.response?.status === 429) {
          // Rate limit hit - wait and retry
          const retryAfter = error.response.headers['retry-after'] || 1;
          await this.sleep(retryAfter * 1000);
          return this.client.request(error.config);
        }
        return Promise.reject(error);
      }
    );
  }

  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  private async checkRateLimit(): Promise<void> {
    if (this.rateLimitRemaining < 5 && Date.now() < this.rateLimitReset) {
      const waitTime = this.rateLimitReset - Date.now();
      await this.sleep(waitTime);
    }
  }

  private handleError(error: unknown): never {
    if (axios.isAxiosError(error)) {
      const axiosError = error as AxiosError<WrikeError>;
      
      if (axiosError.response?.data) {
        const wrikeError = axiosError.response.data;
        throw new Error(
          `Wrike API Error: ${wrikeError.error} - ${wrikeError.errorDescription}`
        );
      }
      
      throw new Error(
        `HTTP ${axiosError.response?.status || 'Unknown'}: ${axiosError.message}`
      );
    }
    
    throw error;
  }

  private buildQueryString(params?: WrikeQueryParams): string {
    if (!params) return '';
    
    const queryParts: string[] = [];
    
    Object.entries(params).forEach(([key, value]) => {
      if (value === undefined || value === null) return;
      
      if (Array.isArray(value)) {
        queryParts.push(`${key}=${JSON.stringify(value)}`);
      } else if (typeof value === 'object') {
        queryParts.push(`${key}=${JSON.stringify(value)}`);
      } else {
        queryParts.push(`${key}=${encodeURIComponent(String(value))}`);
      }
    });
    
    return queryParts.length > 0 ? `?${queryParts.join('&')}` : '';
  }

  // Generic request methods
  async get<T>(endpoint: string, params?: WrikeQueryParams): Promise<WrikeApiResponse<T>> {
    await this.checkRateLimit();
    
    try {
      const queryString = this.buildQueryString(params);
      const response = await this.client.get<WrikeApiResponse<T>>(`${endpoint}${queryString}`);
      return response.data;
    } catch (error) {
      this.handleError(error);
    }
  }

  async post<T>(endpoint: string, data?: unknown, params?: WrikeQueryParams): Promise<WrikeApiResponse<T>> {
    await this.checkRateLimit();
    
    try {
      const queryString = this.buildQueryString(params);
      const response = await this.client.post<WrikeApiResponse<T>>(`${endpoint}${queryString}`, data);
      return response.data;
    } catch (error) {
      this.handleError(error);
    }
  }

  async put<T>(endpoint: string, data?: unknown, params?: WrikeQueryParams): Promise<WrikeApiResponse<T>> {
    await this.checkRateLimit();
    
    try {
      const queryString = this.buildQueryString(params);
      const response = await this.client.put<WrikeApiResponse<T>>(`${endpoint}${queryString}`, data);
      return response.data;
    } catch (error) {
      this.handleError(error);
    }
  }

  async delete<T>(endpoint: string, params?: WrikeQueryParams): Promise<WrikeApiResponse<T>> {
    await this.checkRateLimit();
    
    try {
      const queryString = this.buildQueryString(params);
      const response = await this.client.delete<WrikeApiResponse<T>>(`${endpoint}${queryString}`);
      return response.data;
    } catch (error) {
      this.handleError(error);
    }
  }

  async uploadAttachment(
    endpoint: string,
    file: Buffer,
    filename: string,
    contentType?: string
  ): Promise<WrikeApiResponse<unknown>> {
    await this.checkRateLimit();
    
    try {
      const formData = new FormData();
      formData.append('file', file, {
        filename,
        contentType: contentType || 'application/octet-stream',
      });

      const response = await this.client.post(endpoint, formData, {
        headers: {
          ...formData.getHeaders(),
        },
      });
      
      return response.data;
    } catch (error) {
      this.handleError(error);
    }
  }

  async downloadAttachment(url: string): Promise<Buffer> {
    await this.checkRateLimit();
    
    try {
      const response = await this.client.get(url, {
        responseType: 'arraybuffer',
      });
      return Buffer.from(response.data);
    } catch (error) {
      this.handleError(error);
    }
  }

  // Pagination helper
  async *paginate<T>(
    endpoint: string,
    params?: WrikeQueryParams,
    pageSize = 100
  ): AsyncGenerator<T[], void, unknown> {
    let nextPageToken: string | undefined;
    
    do {
      const paginatedParams = {
        ...params,
        pageSize,
        nextPageToken,
      };
      
      const response = await this.get<T>(endpoint, paginatedParams);
      
      if (response.data && response.data.length > 0) {
        yield response.data;
      }
      
      // Check for next page token in response metadata
      // Wrike doesn't use standard pagination, but this pattern is ready if needed
      nextPageToken = undefined;
      
      if (!nextPageToken || response.data.length < pageSize) {
        break;
      }
    } while (nextPageToken);
  }

  // Batch operations helper
  async batchGet<T>(endpoint: string, ids: string[], params?: WrikeQueryParams): Promise<T[]> {
    const batchSize = 100; // Wrike's typical batch limit
    const results: T[] = [];
    
    for (let i = 0; i < ids.length; i += batchSize) {
      const batch = ids.slice(i, i + batchSize);
      const batchEndpoint = `${endpoint}/${batch.join(',')}`;
      const response = await this.get<T>(batchEndpoint, params);
      results.push(...response.data);
    }
    
    return results;
  }

  // Health check
  async testConnection(): Promise<boolean> {
    try {
      await this.get('/contacts');
      return true;
    } catch (error) {
      return false;
    }
  }

  // Get rate limit status
  getRateLimitStatus(): { remaining: number; resetAt: number } {
    return {
      remaining: this.rateLimitRemaining,
      resetAt: this.rateLimitReset,
    };
  }
}
