import { useServiceStore } from "@/store/service.store";
import { useToastStore } from "@/store/toast.store";
import { getAuthToken } from "@/lib/utils";

const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? "";

// Validate BASE_URL is a trusted origin — prevents SSRF
const TRUSTED_ORIGINS = [
  process.env.NEXT_PUBLIC_API_BASE_URL,
  process.env.NEXT_PUBLIC_AUTH_BASE_URL,
].filter(Boolean);

function isTrustedUrl(url: string): boolean {
  try {
    const parsed = new URL(url);
    // Only allow http/https
    if (!['http:', 'https:'].includes(parsed.protocol)) return false;
    // Must match one of the configured trusted origins
    return TRUSTED_ORIGINS.some((origin) => {
      try { return new URL(origin!).origin === parsed.origin; } catch { return false; }
    });
  } catch {
    return false;
  }
}

export class ApiClientError extends Error {
  constructor(
    public readonly status: number,
    message: string,
    public readonly toastShown = false
  ) {
    super(message);
    this.name = "ApiClientError";
  }
}

type RequestOptions = Omit<RequestInit, "body"> & {
  params?: Record<string, string>;
  body?: unknown;
  toast?: boolean | {
    success?: boolean;
    error?: boolean;
  };
};

type ApiMessageResponse = {
  success?: boolean;
  message?: string;
};

function shouldShowToast(
  toast: RequestOptions["toast"],
  variant: "success" | "error",
  method: string
): boolean {
  if (toast === false) return false;
  if (typeof toast === "object") {
    return toast[variant] ?? (variant === "error" || method !== "GET");
  }
  return variant === "error" || method !== "GET";
}

function showApiToast(
  variant: "success" | "error",
  message: string,
  toast: RequestOptions["toast"],
  method: string
) {
  if (typeof window === "undefined") return;
  if (!shouldShowToast(toast, variant, method)) return;

  useToastStore.getState().add({
    variant,
    title: variant === "success" ? "Success" : "Error",
    description: message,
  });
}

async function request<T>(endpoint: string, options: RequestOptions = {}): Promise<T> {
  const { params, body, headers, toast, ...rest } = options;
  const method = rest.method ?? "GET";

  let urlString = endpoint.startsWith("http")
    ? endpoint
    : `${BASE_URL}${endpoint}`;

  // SSRF protection — validate final URL is a trusted origin
  if (!isTrustedUrl(urlString)) {
    throw new Error(`Blocked request to untrusted URL: ${urlString}`);
  }

  if (params && Object.keys(params).length > 0) {
    urlString = `${urlString}?${new URLSearchParams(params).toString()}`;
  }

  // Read token — using getAuthToken helper (handles sessionStorage and cookie)
  const token = getAuthToken() || (typeof window !== "undefined" ? localStorage.getItem("auth_token") : null);

  try {
    const response = await fetch(urlString, {
      ...rest,
      headers: {
        "Content-Type": "application/json",
        "ngrok-skip-browser-warning": "true",
        ...(token ? { Authorization: `Bearer ${token}` } : {}),
        ...headers,
      },
      ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
    });

    // Request succeeded — mark service as up
    useServiceStore.getState().setDown(false);

    if (!response.ok) {
      // 401 — token expired, clear session and redirect
      if (response.status === 401 && typeof window !== "undefined") {
        sessionStorage.removeItem("auth_token");
        sessionStorage.removeItem("auth_email");
        document.cookie = "auth_token=; path=/; max-age=0";
        window.location.href = "/login";
        return undefined as T;
      }

      let message = `Request failed with status ${response.status}`;
      try {
        const data = await response.json();
        message = data?.message ?? data?.detail ?? message;
      } catch {
        // non-JSON error body
      }
      showApiToast("error", message, toast, method);
      throw new ApiClientError(response.status, message, true);
    }

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

    const data = await response.json();
    const apiMessage = data as ApiMessageResponse;

    if (typeof apiMessage.success === "boolean" && apiMessage.message) {
      showApiToast(apiMessage.success ? "success" : "error", apiMessage.message, toast, method);
    }

    return data as T;

  } catch (err) {
    // TypeError: Failed to fetch = network error / server down
    if (err instanceof TypeError && err.message.toLowerCase().includes("fetch")) {
      useServiceStore.getState().setDown(true);
      showApiToast("error", "Service unavailable", toast, method);
      throw new ApiClientError(0, "Service unavailable", true);
    }
    throw err;
  }
}

export const apiClient = {
  get: <T>(endpoint: string, options?: Omit<RequestOptions, "body">) =>
    request<T>(endpoint, { ...options, method: "GET" }),

  post: <T>(endpoint: string, body?: unknown, options?: RequestOptions) =>
    request<T>(endpoint, { ...options, method: "POST", body }),

  put: <T>(endpoint: string, body?: unknown, options?: RequestOptions) =>
    request<T>(endpoint, { ...options, method: "PUT", body }),

  patch: <T>(endpoint: string, body?: unknown, options?: RequestOptions) =>
    request<T>(endpoint, { ...options, method: "PATCH", body }),

  delete: <T>(endpoint: string, options?: RequestOptions) =>
    request<T>(endpoint, { ...options, method: "DELETE" }),
};
