import { z } from "zod";

import { devLogger } from "../utils/logger";
import { env } from "@/lib/env";

export class ApiError extends Error {
  constructor(
    public status: number,
    public data: unknown,
    message = "API Error"
  ) {
    super(message);
    this.name = "ApiError";
  }
}

type RequestOpts = Omit<RequestInit, "method" | "body"> & {
  tags?: string[]; // next.js cache-tag
  query?: Record<string, unknown>; // query-string
  schema?: z.ZodTypeAny; // runtime validation
  revalidate?: number;
  skipAuthRedirect?: boolean;
};

class HttpClient {
  constructor(private baseUrl: string) {}

  private buildUrl(
    rootUrl: string,
    path: string,
    query?: Record<string, unknown>
  ) {
    const url = new URL(path, rootUrl);
    if (query)
      Object.entries(query).forEach(([k, v]) =>
        url.searchParams.append(k, String(v))
      );
    return url.toString();
  }

  private async request<T>(
    method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
    rootUrl: string = this.baseUrl,
    path: string,
    opts: RequestOpts = {},
    body?: unknown,
    errorMessage?: string
  ): Promise<T> {
    const {
      tags = [],
      schema,
      query,
      revalidate = 300,
      skipAuthRedirect = false,
      ...rest
    } = opts;

    const fullUrl = this.buildUrl(rootUrl, path, query);
    const startTime = Date.now();

    // Log API request (both client and server with ENV control)
    if (typeof window !== "undefined") {
      // Client-side logging
      devLogger.apiRequest(fullUrl, method, body);
    } else {
      // Server-side logging (requires ENV variable)
      if (typeof devLogger.serverApiRequest === "function") {
        devLogger.serverApiRequest(fullUrl, method, body);
      } else {
        // Fallback server logging
        if (env.DEV_LOGGER_SERVER_ENABLED === "true") {
          console.group(`\n🚀 [SERVER] API REQUEST: ${method} ${fullUrl}`);
          if (body !== undefined) {
            console.log("📦 Request Body:", JSON.stringify(body, null, 2));
          }
          console.groupEnd();
        }
      }
    }

    const headers = new Headers();

    let sessionId: string | null = null;
    if (typeof window === "undefined") {
      const { getServerSessionId } = await import("../session/sessionId");
      sessionId = (await getServerSessionId()) ?? null;
    } else {
      try {
        const { getClientSessionId } = await import("../session/sessionId");
        sessionId = getClientSessionId();
      } catch {
        // ignore
      }
    }
    if (sessionId) headers.set("session-id", sessionId);

    let accessToken: string | undefined;
    if (typeof window === "undefined") {
      const { getServerAccessToken } = await import("../auth/token");
      accessToken = await getServerAccessToken();
    } else {
      try {
        const { getAuthTokenFromCookie } = await import(
          "@/entities/user/serverActions"
        );
        accessToken = await getAuthTokenFromCookie();
      } catch {
        // ignore
      }
    }

    if (accessToken) headers.set("Authorization", `Bearer ${accessToken}`);
    headers.set("Content-Type", "application/json");

    const res = await fetch(fullUrl, {
      method,
      body: body ? JSON.stringify(body) : undefined,
      headers,
      next: { revalidate, tags },
      ...rest,
    });

    const payload = await res.json().catch(() => null);
    const duration = Date.now() - startTime;

    if (!res.ok) {
      // Log API error response (both client and server)
      if (typeof window !== "undefined") {
        devLogger.apiResponse(fullUrl, method, res.status, payload, duration);
      } else {
        if (typeof devLogger.serverApiResponse === "function") {
          devLogger.serverApiResponse(
            fullUrl,
            method,
            res.status,
            payload,
            duration
          );
        } else {
          // Fallback server logging
          if (env.DEV_LOGGER_SERVER_ENABLED === "true") {
            const emoji = res.status >= 200 && res.status < 300 ? "✅" : "❌";
            console.group(
              `\n${emoji} [SERVER] API ERROR: ${method} ${fullUrl}`
            );
            console.log(`📊 Status: ${res.status}`);
            console.log(`⏱️ Duration: ${duration}ms`);
            if (payload !== undefined) {
              console.log("📦 Error Response:", payload);
            }
            console.groupEnd();
          }
        }
      }

      if (res.status === 401 && !skipAuthRedirect) {
        if (typeof window === "undefined") {
          const { redirect } = await import("next/navigation");
          redirect(env.NEXT_PUBLIC_AUTH_REDIRECT_URL || "");
        } else {
          const url = env.NEXT_PUBLIC_AUTH_REDIRECT_URL || "/";
          window.location.href = url;
        }
      }
      throw new ApiError(res.status, payload, errorMessage);
    }

    const data = payload as T;
    const validatedData = schema ? (schema.parse(data) as T) : data;

    // Log successful API response (both client and server)
    if (typeof window !== "undefined") {
      devLogger.apiResponse(
        fullUrl,
        method,
        res.status,
        validatedData,
        duration
      );
    } else {
      if (typeof devLogger.serverApiResponse === "function") {
        devLogger.serverApiResponse(
          fullUrl,
          method,
          res.status,
          validatedData,
          duration
        );
      } else {
        // Fallback server logging
        if (env.DEV_LOGGER_SERVER_ENABLED === "true") {
          console.group(`\n✅ [SERVER] API SUCCESS: ${method} ${fullUrl}`);
          console.log(`📊 Status: ${res.status}`);
          console.log(`⏱️ Duration: ${duration}ms`);
          if (validatedData !== undefined) {
            const responsePreview =
              typeof validatedData === "object" && validatedData !== null
                ? Array.isArray(validatedData)
                  ? `Array[${validatedData.length}]`
                  : `Object{${Object.keys(validatedData)
                      .slice(0, 5)
                      .join(", ")}${
                      Object.keys(validatedData).length > 5 ? "..." : ""
                    }}`
                : validatedData;
            console.log("📦 Response Preview:", responsePreview);
          }
          console.groupEnd();
        }
      }
    }

    return validatedData;
  }

  get = <T>(p: string, o?: RequestOpts, u?: string) =>
    this.request<T>("GET", u, p, o);
  post = <T>(p: string, b: unknown, o?: RequestOpts, u?: string) =>
    this.request<T>("POST", u, p, o, b);
  put = <T>(p: string, b: unknown, o?: RequestOpts, u?: string) =>
    this.request<T>("PUT", u, p, o, b);
  patch = <T>(p: string, b: unknown, o?: RequestOpts, u?: string) =>
    this.request<T>("PATCH", u, p, o, b);
  delete = <T>(p: string, o?: RequestOpts, u?: string) =>
    this.request<T>("DELETE", u, p, o);
}

const apiUrl = env.NEXT_PUBLIC_API_URL;
if (!apiUrl) {
  throw new Error("NEXT_PUBLIC_API_URL environment variable is required");
}

export const http = new HttpClient(apiUrl);
