import { deepNormalize } from '../utils/normalize';
const API_BASE = '/api';

export class HttpClient {
  private token: string | null = null;
  private isRefreshing = false;
  private refreshSubscribers: Array<(token: string) => void> = [];

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

  private subscribeTokenRefresh(cb: (token: string) => void) {
    this.refreshSubscribers.push(cb);
  }

  private onRefreshed(token: string) {
    this.refreshSubscribers.forEach(cb => cb(token));
    this.refreshSubscribers = [];
  }

  private async handle401(): Promise<string | null> {
    if (this.isRefreshing) {
      return new Promise(resolve => {
        this.subscribeTokenRefresh(token => {
          resolve(token);
        });
      });
    }

    const refreshToken = localStorage.getItem('knot_refresh_token');
    if (!refreshToken) {
      return null;
    }

    this.isRefreshing = true;

    try {
      const response = await fetch(`${API_BASE}/auth/refresh`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ refreshToken }),
      });

      if (!response.ok) {
        localStorage.removeItem('knot_token');
        localStorage.removeItem('knot_refresh_token');
        this.token = null;
        return null;
      }

      const data = await response.json();
      const newToken = data.accessToken;
      const newRefreshToken = data.refreshToken;

      localStorage.setItem('knot_token', newToken);
      if (newRefreshToken) {
        localStorage.setItem('knot_refresh_token', newRefreshToken);
      }

      this.token = newToken;
      this.onRefreshed(newToken);
      return newToken;
    } catch (err) {
      localStorage.removeItem('knot_token');
      localStorage.removeItem('knot_refresh_token');
      this.token = null;
      return null;
    } finally {
      this.isRefreshing = false;
    }
  }

  async request<T>(endpoint: string, options: RequestInit & { timeout?: number } = {}): Promise<T> {
    const { timeout = 30_000, ...fetchOptions } = options;
    const controller = new AbortController();
    const timer = timeout > 0 ? setTimeout(() => controller.abort(), timeout) : undefined;

    const isFormData = fetchOptions.body instanceof FormData;
    const computedHeaders: Record<string, string> = {
      ...(this.token
        ? { Authorization: this.token.startsWith('Basic ') ? this.token : `Bearer ${this.token}` }
        : {}),
      ...(fetchOptions.headers as Record<string, string>),
    };

    if (!isFormData && !computedHeaders['Content-Type']) {
      computedHeaders['Content-Type'] = 'application/json';
    }

    let response: Response;
    try {
      response = await fetch(`${API_BASE}${endpoint}`, {
        ...fetchOptions,
        headers: computedHeaders,
        signal: controller.signal,
      });
    } catch (err) {
      clearTimeout(timer);
      if (err instanceof DOMException && err.name === 'AbortError') {
        throw new Error('Время ожидания запроса истекло');
      }
      throw err;
    }
    clearTimeout(timer);

    // Handle 401 Unauthorized
    if (response.status === 401 && endpoint !== '/auth/refresh') {
      const newToken = await this.handle401();
      if (newToken) {
        // Retry the original request with new token
        const retryHeaders: Record<string, string> = {
          ...computedHeaders,
          Authorization: `Bearer ${newToken}`,
        };

        const retryController = new AbortController();
        const retryTimer = timeout > 0 ? setTimeout(() => retryController.abort(), timeout) : undefined;

        try {
          response = await fetch(`${API_BASE}${endpoint}`, {
            ...fetchOptions,
            headers: retryHeaders,
            signal: retryController.signal,
          });
        } catch (err) {
          clearTimeout(retryTimer);
          if (err instanceof DOMException && err.name === 'AbortError') {
            throw new Error('Время ожидания запроса истекло');
          }
          throw err;
        }
        clearTimeout(retryTimer);
      }
    }

    if (!response.ok) {
      const errorData = await response.json().catch(() => ({}));
      const errorMessage = errorData.error || errorData.message || `Request failed with status ${response.status}`;
      const err = new Error(errorMessage);
      (err as any).status = response.status;
      throw err;
    }

    // Handle empty responses (e.g., 200 OK with no body)
    const text = await response.text();
    if (!text) {
      return undefined as T;
    }

    const jsonData = JSON.parse(text);
    return deepNormalize(jsonData);
  }
}

export const httpClient = new HttpClient();
