/**
 * Core HTTP client for React Query integration
 * 
 * Architecture:
 * - Axios: HTTP client for making requests
 * - This Client: Handles auth tokens, tenant context, and error formatting
 * - Services: Business logic that uses this client
 * - React Query Hooks: Data fetching layer that uses services
 * 
 * React Query doesn't replace HTTP clients - it manages data fetching/caching/sync
 */
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';

export interface ApiError {
  message: string;
  status?: number;
  code?: string;
  details?: any;
}

export interface SubscriptionError {
  error: string;
  message: string;
  code: string;
  details: {
    required_feature: string;
    required_level: string;
    current_plan: string;
    upgrade_url: string;
  };
}

// Subscription error event emitter
class SubscriptionErrorEmitter extends EventTarget {
  emitSubscriptionError(error: SubscriptionError) {
    this.dispatchEvent(new CustomEvent('subscriptionError', { detail: error }));
  }
}

export const subscriptionErrorEmitter = new SubscriptionErrorEmitter();

class ApiClient {
  private client: AxiosInstance;
  private baseURL: string;
  private authToken: string | null = null;
  private tenantId: string | null = null;
  private refreshToken: string | null = null;
  private isRefreshing: boolean = false;
  private refreshAttempts: number = 0;
  private maxRefreshAttempts: number = 3;
  private lastRefreshAttempt: number = 0;
  private failedQueue: Array<{
    resolve: (value?: any) => void;
    reject: (error?: any) => void;
    config: AxiosRequestConfig;
  }> = [];

  constructor(baseURL: string = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000/api/v1') {
    this.baseURL = baseURL;
    
    this.client = axios.create({
      baseURL: this.baseURL,
      timeout: 30000,
      headers: {
        'Content-Type': 'application/json',
      },
    });

    this.setupInterceptors();
  }

  private setupInterceptors() {
    // Request interceptor to add auth headers
    this.client.interceptors.request.use(
      (config) => {
        if (this.authToken) {
          config.headers.Authorization = `Bearer ${this.authToken}`;
        }
        
        if (this.tenantId) {
          config.headers['X-Tenant-ID'] = this.tenantId;
        }

        return config;
      },
      (error) => {
        return Promise.reject(this.handleError(error));
      }
    );

    // Response interceptor for error handling and automatic token refresh
    this.client.interceptors.response.use(
      (response) => response,
      async (error) => {
        const originalRequest = error.config;

        // Check if error is 401 and we have a refresh token
        if (error.response?.status === 401 && this.refreshToken && !originalRequest._retry) {
          // Check if we've exceeded max refresh attempts in a short time
          const now = Date.now();
          if (this.refreshAttempts >= this.maxRefreshAttempts && (now - this.lastRefreshAttempt) < 30000) {
            console.log('Max refresh attempts exceeded, logging out');
            await this.handleAuthFailure();
            return Promise.reject(this.handleError(error));
          }

          if (this.isRefreshing) {
            // If already refreshing, queue this request
            return new Promise((resolve, reject) => {
              this.failedQueue.push({ resolve, reject, config: originalRequest });
            });
          }

          originalRequest._retry = true;
          this.isRefreshing = true;
          this.refreshAttempts++;
          this.lastRefreshAttempt = now;

          try {
            console.log(`Attempting token refresh (attempt ${this.refreshAttempts})...`);

            // Attempt to refresh the token
            const response = await this.client.post('/auth/refresh', {
              refresh_token: this.refreshToken
            });

            const { access_token, refresh_token } = response.data;

            console.log('Token refresh successful');

            // Reset refresh attempts on success
            this.refreshAttempts = 0;

            // Update tokens
            this.setAuthToken(access_token);
            if (refresh_token) {
              this.setRefreshToken(refresh_token);
            }

            // Update auth store if available
            await this.updateAuthStore(access_token, refresh_token);

            // Process failed queue
            this.processQueue(null, access_token);

            // Retry original request with new token
            originalRequest.headers.Authorization = `Bearer ${access_token}`;
            return this.client(originalRequest);

          } catch (refreshError) {
            console.error(`Token refresh failed (attempt ${this.refreshAttempts}):`, refreshError);
            // Refresh failed, clear tokens and redirect to login
            this.processQueue(refreshError, null);
            await this.handleAuthFailure();
            return Promise.reject(this.handleError(refreshError as AxiosError));
          } finally {
            this.isRefreshing = false;
          }
        }

        return Promise.reject(this.handleError(error));
      }
    );
  }

  private handleError(error: AxiosError): ApiError {
    if (error.response) {
      // Server responded with error status
      const { status, data } = error.response;

      // Check for subscription errors
      if (status === 403 && (data as any)?.code === 'SUBSCRIPTION_UPGRADE_REQUIRED') {
        const subscriptionError = data as SubscriptionError;
        subscriptionErrorEmitter.emitSubscriptionError(subscriptionError);
      }

      return {
        message: (data as any)?.detail || (data as any)?.message || `Request failed with status ${status}`,
        status,
        code: (data as any)?.code,
        details: data,
      };
    } else if (error.request) {
      // Network error
      return {
        message: 'Network error - please check your connection',
        status: 0,
      };
    } else {
      // Other error
      return {
        message: error.message || 'Unknown error occurred',
      };
    }
  }

  private processQueue(error: any, token: string | null = null) {
    this.failedQueue.forEach(({ resolve, reject, config }) => {
      if (error) {
        reject(error);
      } else {
        if (token) {
          config.headers = config.headers || {};
          config.headers.Authorization = `Bearer ${token}`;
        }
        resolve(this.client(config));
      }
    });

    this.failedQueue = [];
  }

  private async updateAuthStore(accessToken: string, refreshToken?: string) {
    try {
      // Dynamically import to avoid circular dependency
      const { useAuthStore } = await import('../../stores/auth.store');
      const setState = useAuthStore.setState;

      // Update the store with new tokens
      setState(state => ({
        ...state,
        token: accessToken,
        refreshToken: refreshToken || state.refreshToken,
      }));
    } catch (error) {
      console.warn('Failed to update auth store:', error);
    }
  }

  private async handleAuthFailure() {
    try {
      // Clear tokens
      this.setAuthToken(null);
      this.setRefreshToken(null);

      // Dynamically import to avoid circular dependency
      const { useAuthStore } = await import('../../stores/auth.store');
      const store = useAuthStore.getState();

      // Logout user
      store.logout();

      // Redirect to login if not already there
      if (typeof window !== 'undefined' && !window.location.pathname.includes('/login')) {
        window.location.href = '/login';
      }
    } catch (error) {
      console.warn('Failed to handle auth failure:', error);
    }
  }

  // Configuration methods
  setAuthToken(token: string | null) {
    this.authToken = token;
  }

  setRefreshToken(token: string | null) {
    this.refreshToken = token;
  }

  setTenantId(tenantId: string | null) {
    this.tenantId = tenantId;
  }

  getAuthToken(): string | null {
    return this.authToken;
  }

  getRefreshToken(): string | null {
    return this.refreshToken;
  }

  getTenantId(): string | null {
    return this.tenantId;
  }

  // HTTP Methods - Return direct data for React Query
  async get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response: AxiosResponse<T> = await this.client.get(url, config);
    return response.data;
  }

  async post<T = any, D = any>(
    url: string,
    data?: D,
    config?: AxiosRequestConfig
  ): Promise<T> {
    const response: AxiosResponse<T> = await this.client.post(url, data, config);
    return response.data;
  }

  async put<T = any, D = any>(
    url: string,
    data?: D,
    config?: AxiosRequestConfig
  ): Promise<T> {
    const response: AxiosResponse<T> = await this.client.put(url, data, config);
    return response.data;
  }

  async patch<T = any, D = any>(
    url: string,
    data?: D,
    config?: AxiosRequestConfig
  ): Promise<T> {
    const response: AxiosResponse<T> = await this.client.patch(url, data, config);
    return response.data;
  }

  async delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response: AxiosResponse<T> = await this.client.delete(url, config);
    return response.data;
  }

  // File upload helper
  async uploadFile<T = any>(
    url: string,
    file: File | FormData,
    config?: AxiosRequestConfig
  ): Promise<T> {
    const formData = file instanceof FormData ? file : new FormData();
    if (file instanceof File) {
      formData.append('file', file);
    }

    return this.post<T>(url, formData, {
      ...config,
      headers: {
        ...config?.headers,
        'Content-Type': 'multipart/form-data',
      },
    });
  }

  // Raw axios instance for advanced usage
  getAxiosInstance(): AxiosInstance {
    return this.client;
  }
}

// Create and export singleton instance
export const apiClient = new ApiClient();
export default apiClient;