/**
 * API Client for communicating with Backend via Envoy Gateway
 */

const resolveApiBaseUrl = (): string => {
  const publicApiUrl = process.env.NEXT_PUBLIC_API_URL;
  const serverApiUrl =
    typeof window === "undefined" ? process.env.ENVOY_GATEWAY_URL : undefined;

  return publicApiUrl || serverApiUrl || "http://node.crop-logic.ir";
};

const AUTH_STORAGE_KEYS = {
  accessToken: "auth_token",
  refreshToken: "auth_refresh_token",
} as const;

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

export class ApiClient {
  private baseURL: string;
  private defaultHeaders: Record<string, string>;

  constructor(baseURL: string = resolveApiBaseUrl()) {
    this.baseURL = baseURL.replace(/\/$/, ""); // Remove trailing slash
    this.defaultHeaders = {
      Accept: "application/json",
      "Content-Type": "application/json",
    };
  }

  /**
   * Get authorization token from localStorage
   */
  private getAuthToken(): string | null {
    if (typeof window === "undefined") return null;
    return localStorage.getItem(AUTH_STORAGE_KEYS.accessToken);
  }

  /**
   * Set authorization token in localStorage
   */
  setAuthToken(token: string): void {
    if (typeof window !== "undefined") {
      localStorage.setItem(AUTH_STORAGE_KEYS.accessToken, token);
    }
  }

  /**
   * Set refresh token in localStorage
   */
  setRefreshToken(token: string): void {
    if (typeof window !== "undefined") {
      localStorage.setItem(AUTH_STORAGE_KEYS.refreshToken, token);
    }
  }

  /**
   * Set access and refresh tokens in localStorage
   */
  setAuthTokens(tokens: { access: string; refresh?: string }): void {
    this.setAuthToken(tokens.access);

    if (tokens.refresh) {
      this.setRefreshToken(tokens.refresh);
    }
  }

  /**
   * Clear authorization token
   */
  clearAuthToken(): void {
    if (typeof window !== "undefined") {
      localStorage.removeItem(AUTH_STORAGE_KEYS.accessToken);
      localStorage.removeItem(AUTH_STORAGE_KEYS.refreshToken);
    }
  }

  /**
   * Get headers with authentication token
   */
  private getHeaders(
    customHeaders?: Record<string, string>,
  ): Record<string, string> {
    const headers = { ...this.defaultHeaders, ...customHeaders };
    const token = this.getAuthToken();

    if (token) {
      headers["Authorization"] = `Bearer ${token}`;
    }

    return headers;
  }

  private prepareBody(
    data: any,
    headers: Record<string, string>,
  ): BodyInit | undefined {
    if (data === undefined || data === null) return undefined;

    if (typeof FormData !== "undefined" && data instanceof FormData) {
      delete headers["Content-Type"];
      return data;
    }

    if (
      typeof URLSearchParams !== "undefined" &&
      data instanceof URLSearchParams
    ) {
      return data;
    }

    if (typeof Blob !== "undefined" && data instanceof Blob) {
      return data;
    }

    if (typeof ArrayBuffer !== "undefined" && data instanceof ArrayBuffer) {
      return data;
    }

    if (typeof ArrayBuffer !== "undefined" && ArrayBuffer.isView(data)) {
      return data;
    }

    const contentType = headers["Content-Type"]?.toLowerCase();

    if (contentType?.includes("application/x-www-form-urlencoded")) {
      return new URLSearchParams(
        Object.entries(data).reduce<Record<string, string>>((acc, [key, value]) => {
          if (value !== undefined && value !== null) {
            acc[key] = String(value);
          }

          return acc;
        }, {}),
      ).toString();
    }

    if (!contentType || contentType.includes("application/json")) {
      return typeof data === "string" ? data : JSON.stringify(data);
    }

    return data as BodyInit;
  }

  /**
   * Handle API response
   */
  private async handleResponse<T>(response: Response): Promise<T> {
    if (!response.ok) {
      let errorData: any;
      try {
        errorData = await response.json();
      } catch {
        errorData = { message: response.statusText };
      }

      const error: ApiError = {
        message: errorData.msg || errorData.message || "An error occurred",
        code: errorData.code || response.status,
        details: errorData,
      };

      // If unauthorized, clear token
      if (response.status === 401) {
        this.clearAuthToken();
      }

      throw error;
    }

    // Handle empty responses
    const contentType = response.headers.get("content-type");
    if (!contentType || !contentType.includes("application/json")) {
      return {} as T;
    }

    return response.json();
  }

  /**
   * GET request
   */
  async get<T>(
    endpoint: string,
    customHeaders?: Record<string, string>,
  ): Promise<T> {
    const url = `${this.baseURL}${endpoint}`;
    const response = await fetch(url, {
      method: "GET",
      headers: this.getHeaders(customHeaders),
    });

    return this.handleResponse<T>(response);
  }

  /**
   * POST request
   */
  async post<T>(
    endpoint: string,
    data?: any,
    customHeaders?: Record<string, string>,
  ): Promise<T> {
    const url = `${this.baseURL}${endpoint}`;
    const headers = this.getHeaders(customHeaders);

    const response = await fetch(url, {
      method: "POST",
      headers,
      body: this.prepareBody(data, headers),
    });

    return this.handleResponse<T>(response);
  }

  /**
   * POST request with FormData (e.g. file upload). Does not set Content-Type so browser sets multipart/form-data.
   */
  async postFormData<T>(
    endpoint: string,
    formData: FormData,
    customHeaders?: Record<string, string>,
  ): Promise<T> {
    const url = `${this.baseURL}${endpoint}`;
    const headers = this.getHeaders(customHeaders);
    const response = await fetch(url, {
      method: "POST",
      headers,
      body: this.prepareBody(formData, headers),
    });

    return this.handleResponse<T>(response);
  }

  /**
   * PUT request
   */
  async put<T>(
    endpoint: string,
    data?: any,
    customHeaders?: Record<string, string>,
  ): Promise<T> {
    const url = `${this.baseURL}${endpoint}`;
    const headers = this.getHeaders(customHeaders);

    const response = await fetch(url, {
      method: "PUT",
      headers,
      body: this.prepareBody(data, headers),
    });

    return this.handleResponse<T>(response);
  }

  /**
   * PATCH request
   */
  async patch<T>(
    endpoint: string,
    data?: any,
    customHeaders?: Record<string, string>,
  ): Promise<T> {
    const url = `${this.baseURL}${endpoint}`;
    const headers = this.getHeaders(customHeaders);

    const response = await fetch(url, {
      method: "PATCH",
      headers,
      body: this.prepareBody(data, headers),
    });

    return this.handleResponse<T>(response);
  }

  /**
   * DELETE request
   */
  async delete<T>(
    endpoint: string,
    customHeaders?: Record<string, string>,
  ): Promise<T> {
    const url = `${this.baseURL}${endpoint}`;
    const response = await fetch(url, {
      method: "DELETE",
      headers: this.getHeaders(customHeaders),
    });

    return this.handleResponse<T>(response);
  }
}

// Export singleton instance
export const apiClient = new ApiClient();
