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

interface ShopifyClientConfig {
  accessToken: string;
  store: string;
  apiVersion?: string;
  timeout?: number;
}

interface RateLimitInfo {
  current: number;
  max: number;
}

interface PaginatedResponse<T> {
  data: T[];
  pageInfo?: {
    nextPageUrl?: string;
    hasNextPage: boolean;
  };
}

export class ShopifyClient {
  private client: AxiosInstance;
  private store: string;
  private apiVersion: string;
  private rateLimitInfo: RateLimitInfo = { current: 0, max: 40 };

  constructor(config: ShopifyClientConfig) {
    this.store = config.store;
    this.apiVersion = config.apiVersion || '2024-01';
    
    const baseURL = `https://${this.store}.myshopify.com/admin/api/${this.apiVersion}`;

    this.client = axios.create({
      baseURL,
      timeout: config.timeout || 30000,
      headers: {
        'X-Shopify-Access-Token': config.accessToken,
        'Content-Type': 'application/json',
      },
    });

    this.setupInterceptors();
  }

  private setupInterceptors(): void {
    // Request interceptor for logging
    this.client.interceptors.request.use(
      (config) => {
        const timestamp = new Date().toISOString();
        console.log(`[${timestamp}] ${config.method?.toUpperCase()} ${config.url}`);
        return config;
      },
      (error) => {
        console.error('[Request Error]', error.message);
        return Promise.reject(error);
      }
    );

    // Response interceptor for rate limit tracking and error normalization
    this.client.interceptors.response.use(
      (response) => {
        // Parse rate limit header: "12/40" means 12 calls made out of 40 max
        const rateLimitHeader = response.headers['x-shopify-shop-api-call-limit'];
        if (rateLimitHeader) {
          const [current, max] = rateLimitHeader.split('/').map(Number);
          this.rateLimitInfo = { current: current || 0, max: max || 40 };
          
          // Log warning if approaching rate limit
          if (this.rateLimitInfo.current >= this.rateLimitInfo.max * 0.8) {
            console.warn(
              `[Rate Limit Warning] ${this.rateLimitInfo.current}/${this.rateLimitInfo.max} calls used`
            );
          }
        }

        return response;
      },
      (error) => {
        return Promise.reject(this.normalizeError(error));
      }
    );
  }

  private normalizeError(error: unknown): Error {
    if (axios.isAxiosError(error)) {
      const axiosError = error as AxiosError;
      const status = axiosError.response?.status;
      const data = axiosError.response?.data as { errors?: unknown };

      let message = axiosError.message;
      
      if (status === 429) {
        message = 'Shopify API rate limit exceeded. Please retry after a delay.';
      } else if (status === 401) {
        message = 'Authentication failed. Check your access token.';
      } else if (status === 404) {
        message = 'Resource not found.';
      } else if (data?.errors) {
        message = typeof data.errors === 'string' 
          ? data.errors 
          : JSON.stringify(data.errors);
      }

      const normalizedError = new Error(message);
      (normalizedError as Error & { status?: number; code?: string }).status = status;
      (normalizedError as Error & { status?: number; code?: string }).code = axiosError.code;
      return normalizedError;
    }

    return error instanceof Error ? error : new Error(String(error));
  }

  private async retryWithBackoff<T>(
    fn: () => Promise<T>,
    retries = 3,
    baseDelay = 1000
  ): Promise<T> {
    let lastError: Error | undefined;

    for (let attempt = 0; attempt <= retries; attempt++) {
      try {
        return await fn();
      } catch (error) {
        lastError = error instanceof Error ? error : new Error(String(error));
        
        // Don't retry on client errors (4xx except 429)
        const status = (lastError as Error & { status?: number }).status;
        if (status && status >= 400 && status < 500 && status !== 429) {
          throw lastError;
        }

        if (attempt < retries) {
          const delay = baseDelay * Math.pow(2, attempt); // Exponential: 1s, 2s, 4s
          console.log(`[Retry ${attempt + 1}/${retries}] Waiting ${delay}ms...`);
          await new Promise(resolve => setTimeout(resolve, delay));
        }
      }
    }

    throw lastError;
  }

  /**
   * Parse Link header for cursor-based pagination
   */
  private parseLinkHeader(linkHeader?: string): { nextPageUrl?: string } {
    if (!linkHeader) return {};

    const links = linkHeader.split(',').map(link => {
      const parts = link.split(';').map(s => s.trim());
      const url = parts[0];
      const rel = parts[1];
      
      if (!url || !rel) return null;
      
      return {
        url: url.slice(1, -1), // Remove angle brackets
        rel: rel.split('=')[1]?.replace(/"/g, ''),
      };
    }).filter((link): link is { url: string; rel: string | undefined } => link !== null);

    const nextLink = links.find(link => link.rel === 'next');
    return { nextPageUrl: nextLink?.url };
  }

  /**
   * GET request with automatic retry
   */
  async get<T>(path: string, config?: AxiosRequestConfig): Promise<T> {
    return this.retryWithBackoff(async () => {
      const response = await this.client.get<T>(path, config);
      return response.data;
    });
  }

  /**
   * GET request with pagination support
   */
  async list<T>(
    path: string,
    config?: AxiosRequestConfig
  ): Promise<PaginatedResponse<T>> {
    return this.retryWithBackoff(async () => {
      const response = await this.client.get<T[]>(path, config);
      const linkHeader = response.headers['link'];
      const { nextPageUrl } = this.parseLinkHeader(linkHeader);

      return {
        data: response.data,
        pageInfo: {
          nextPageUrl,
          hasNextPage: !!nextPageUrl,
        },
      };
    });
  }

  /**
   * GET all pages automatically (use with caution on large datasets)
   */
  async listAll<T>(
    path: string,
    config?: AxiosRequestConfig,
    maxPages = 10
  ): Promise<T[]> {
    const allData: T[] = [];
    let currentPath = path;
    let pageCount = 0;

    while (currentPath && pageCount < maxPages) {
      const response = await this.retryWithBackoff(async () => {
        const url = currentPath.startsWith('http') 
          ? currentPath 
          : currentPath;
        const res = await this.client.get<T[]>(url, config);
        return res;
      });

      allData.push(...response.data);
      
      const linkHeader = response.headers['link'];
      const { nextPageUrl } = this.parseLinkHeader(linkHeader);
      
      if (!nextPageUrl) break;
      
      // Extract path from full URL
      currentPath = nextPageUrl.replace(this.client.defaults.baseURL || '', '');
      pageCount++;
    }

    return allData;
  }

  /**
   * POST request with automatic retry
   */
  async create<T, D = unknown>(
    path: string,
    data: D,
    config?: AxiosRequestConfig
  ): Promise<T> {
    return this.retryWithBackoff(async () => {
      const response = await this.client.post<T>(path, data, config);
      return response.data;
    });
  }

  /**
   * PUT request with automatic retry
   */
  async update<T, D = unknown>(
    path: string,
    data: D,
    config?: AxiosRequestConfig
  ): Promise<T> {
    return this.retryWithBackoff(async () => {
      const response = await this.client.put<T>(path, data, config);
      return response.data;
    });
  }

  /**
   * DELETE request with automatic retry
   */
  async delete<T = void>(path: string, config?: AxiosRequestConfig): Promise<T> {
    return this.retryWithBackoff(async () => {
      const response = await this.client.delete<T>(path, config);
      return response.data;
    });
  }

  /**
   * GraphQL Admin API request
   */
  async graphql<T = unknown>(
    query: string,
    variables?: Record<string, unknown>
  ): Promise<GraphQLResponse<T>> {
    const graphqlUrl = `https://${this.store}.myshopify.com/admin/api/${this.apiVersion}/graphql.json`;

    return this.retryWithBackoff(async () => {
      const response = await this.client.post<GraphQLResponse<T>>(
        graphqlUrl,
        { query, variables } as GraphQLRequest,
        {
          baseURL: '', // Override baseURL for GraphQL endpoint
        }
      );

      if (response.data.errors && response.data.errors.length > 0) {
        const errorMessages = response.data.errors.map(e => e.message).join(', ');
        throw new Error(`GraphQL Error: ${errorMessages}`);
      }

      return response.data;
    });
  }

  /**
   * Get current rate limit info
   */
  getRateLimitInfo(): RateLimitInfo {
    return { ...this.rateLimitInfo };
  }

  /**
   * Check if approaching rate limit (>80% used)
   */
  isApproachingRateLimit(): boolean {
    return this.rateLimitInfo.current >= this.rateLimitInfo.max * 0.8;
  }
}
