import AsyncStorage from "@react-native-async-storage/async-storage";
import { router } from "expo-router";
import { getItemAsync } from "expo-secure-store";
import apiClient from "./service.api.client";
import {
  ACCESS_TOKEN_PATH,
  API_URL,
  CLIENT_ID,
  CLIENT_SECRET,
  ENDPOINTS,
  REFRESH_TOKEN_PATH,
} from "./service.api.constant";

type RequestOption = {
  method?: string;
  body?: any;
  headers?: Record<string, string>;
};

export abstract class AbstractServiceApi {
  protected async request(path: string, options?: RequestOption): Promise<any> {
    const token = await getItemAsync(ACCESS_TOKEN_PATH);
    const headers = this.buildHeaders(token, options?.headers);

    const fetchOptions: RequestInit = {
      method: options?.method ?? "GET",
      headers,
    };

    if (options?.body && fetchOptions.method !== "GET") {
      fetchOptions.body = JSON.stringify(options.body);
    }

    const res = await fetch(`${API_URL}${path}`, fetchOptions);

    return await this.checkStatus(path, res, options);
  }

  private buildHeaders(
    token?: string | null,
    headers?: Record<string, string>
  ): Record<string, string> {
    return {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
      ...(headers || {}),
    };
  }

  private async checkStatus(
    path: string,
    res: Response,
    options?: RequestOption
  ): Promise<any> {
    switch (res.status) {
      case 401:
        await this.tryRefreshToken();
        return this.request(path, options);
      case 403:
        router.replace("/(error)/NotAllowedProblem");
        return;
      case 500:
        router.replace("/(error)/InternalErrorProblem");
        return;
      case 503:
        router.replace("/(error)/BlockedProblem");
        return;
      default:
        return await res.json();
    }
  }

  private async tryRefreshToken(): Promise<boolean> {
    try {
      const refreshToken = await AsyncStorage.getItem(REFRESH_TOKEN_PATH);

      if (!refreshToken) return false;

      const response = await this.getRefreshTokenResponse(refreshToken);

      if (!response.ok) return false;

      const { accessToken, refreshToken: newRefreshToken } =
        await response.json();

      await AsyncStorage.setItem(ACCESS_TOKEN_PATH, accessToken);
      await AsyncStorage.setItem(REFRESH_TOKEN_PATH, newRefreshToken);

      apiClient.defaults.headers.common[
        "Authorization"
      ] = `Bearer ${accessToken}`;

      return true;
    } catch (e) {
      return false;
    }
  }

  private async getRefreshTokenResponse(
    refreshToken: string
  ): Promise<Response> {
    return await fetch(`${API_URL}/${ENDPOINTS.REFRESH_TOKEN}`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Basic ${btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)}`,
      },
      body: JSON.stringify({ refresh_token: refreshToken }),
    });
  }
}
