import { redirectOnAuthFailure } from "@/utils/auth-redirect";
import { getAccessToken, getRefreshToken, setAccessToken, clearAccessToken } from "@/auth-state";
import type { RequestOptions } from "./types";

// API origin - always use relative URLs so requests go through Vite proxy (no CORS).
// DEV_PROXY_SERVER in .env.local tells Vite where to forward /api/** requests.
// NEVER set VITE_API_BASE_URL - that causes direct cross-origin calls which break CORS.
export const API_ORIGIN: string = "";
export const API_BASE = `/api/v1`;
let refreshInFlight: Promise<string | null> | null = null;

const parseBody = async (response: Response): Promise<unknown> => {
  const contentType = response.headers.get("content-type") || "";
  if (contentType.includes("application/json")) {
    try {
      return await response.json();
    } catch {
      return null;
    }
  }

  const text = await response.text();
  if (!text) {
    return null;
  }

  try {
    return JSON.parse(text) as unknown;
  } catch {
    return text;
  }
};

const getAccessTokenExpiry = (token: string): Date | undefined => {
  try {
    const payloadBase64 = token.split(".")[1];
    if (!payloadBase64) return undefined;
    const normalized = payloadBase64.replace(/-/g, "+").replace(/_/g, "/");
    const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
    const json = atob(padded);
    const payload = JSON.parse(json) as { exp?: number };
    if (!payload.exp) return undefined;
    return new Date(payload.exp * 1000);
  } catch {
    return undefined;
  }
};

const refreshAccessToken = async (): Promise<string | null> => {
  if (refreshInFlight) {
    return refreshInFlight;
  }

  refreshInFlight = (async () => {
    const refreshToken = getRefreshToken();
    if (!refreshToken) {
      return null;
    }

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

    if (!response.ok) {
      if (response.status === 401 || response.status === 403) {
        clearAccessToken();
      }
      return null;
    }

    const data = (await parseBody(response)) as { access_token?: string } | null;
    const newAccessToken = data?.access_token;
    if (!newAccessToken) {
      return null;
    }

    const expiresAt = getAccessTokenExpiry(newAccessToken) ?? new Date(Date.now() + 24 * 60 * 60 * 1000);
    setAccessToken(newAccessToken, expiresAt);
    return newAccessToken;
  })();

  try {
    return await refreshInFlight;
  } finally {
    refreshInFlight = null;
  }
};

export const fetchJson = async <T>(path: string, options: RequestOptions = {}): Promise<T> => {
  let token = getAccessToken();
  const isRefreshEndpoint = path.includes("/auth/refresh");
  if (!token && !isRefreshEndpoint) {
    token = await refreshAccessToken();
  }

  const headers = new Headers(options.headers);
  if (token) {
    headers.set("Authorization", `Bearer ${token}`);
  }

  let body = options.body;
  const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
  if (body !== undefined && body !== null && !isFormData && typeof body !== "string") {
    headers.set("Content-Type", "application/json");
    body = JSON.stringify(body);
  }

  try {
    const response = await fetch(`${API_BASE}${path}`, {
      method: options.method ?? (body ? "POST" : "GET"),
      headers,
      body: body as BodyInit | null | undefined,
      // NOTE: no credentials:"include" - we use Bearer token, not cookies.
      // "include" + wildcard CORS = blocked by browser spec.
    });

    if (!response.ok) {
      if ((response.status === 401 || response.status === 403) && !isRefreshEndpoint) {
        const refreshedAccessToken = await refreshAccessToken();
        if (refreshedAccessToken) {
          const retryHeaders = new Headers(options.headers);
          retryHeaders.set("Authorization", `Bearer ${refreshedAccessToken}`);

          if (body !== undefined && body !== null && !isFormData && typeof body !== "string") {
            retryHeaders.set("Content-Type", "application/json");
          }

          const retryResponse = await fetch(`${API_BASE}${path}`, {
            method: options.method ?? (body ? "POST" : "GET"),
            headers: retryHeaders,
            body: body as BodyInit | null | undefined,
          });

          if (retryResponse.ok) {
            if (retryResponse.status === 204) {
              return undefined as T;
            }
            const retryData = await parseBody(retryResponse);
            return retryData as T;
          }
        }
      }

      // Don't redirect on 401 for /auth/me - it's expected when not logged in
      const isAuthMeEndpoint = path.includes("/auth/me");
      if ((response.status === 401 || response.status === 403) && !isAuthMeEndpoint) {
        redirectOnAuthFailure();
      }
      let message = `${response.status} ${response.statusText}`;
      const data = await parseBody(response);
      if (data && typeof data === "object" && "detail" in data) {
        const detail = (data as { detail?: string }).detail;
        if (detail) {
          message = detail;
        }
      } else if (typeof data === "string" && data.trim()) {
        message = data;
      }
      throw new Error(message);
    }

    if (response.status === 204) {
      return undefined as T;
    }

    const data = await parseBody(response);
    return data as T;
  } catch (error) {
    // Handle network errors (connection refused, timeout, etc.)
    if (error instanceof TypeError && error.message.includes("fetch")) {
      const networkError = new Error(
        `Không thể kết nối đến server. Vui lòng kiểm tra lại kết nối hoặc đảm bảo backend đang chạy tại ${API_ORIGIN}`
      );
      networkError.name = "NetworkError";
      throw networkError;
    }
    // Re-throw other errors as-is
    throw error;
  }
};

export const parseResourceId = (name: string): string => {
  const parts = name.split("/");
  return parts[parts.length - 1] || name;
};

