import { SessionWithTokens, authOptions } from "@/config/authConfig";
import { getServerSession } from "next-auth";
import { headers } from "next/headers";
import { redirect } from "next/navigation";

export interface BlobResponse {
  filename: string;
  blobValue: Uint8Array;
}

export interface Pageable {
  pageSize?: number;
  pageNum?: number;
}

export type SearchParams = {
  searchParams: { [key: string]: string | string[] | undefined };
};

export interface searchParamsProps {
  searchParams: { [key: string]: string | string[] | undefined };
}

export class ServerFetchError extends Error {
  public readonly response: Response | undefined;
  constructor(message?: string, response?: Response) {
    super(message);
    this.response = response;

    Object.setPrototypeOf(this, ServerFetchError.prototype);
  }
}

export async function serverFetchWithNoContent(...args: FetchParams) {
  const response = await serverFetch(...args);

  if (response.ok) {
    return response.status; 
  } else {
    switch (response.status) {
      case 401:
        signOutUser();
      default:
        let errorMessage = "Something went wrong fetching data in server.";
        try {
          const contentType = response.headers.get("content-type");
          if (contentType && contentType.includes("application/json")) {
            const errorJson = await response.json();
            if (errorJson.error) {
              errorMessage = errorJson.error;
            } else if (errorJson.message) {
              errorMessage = errorJson.message;
            } else if (errorJson.traceId) {
              errorMessage = `Error occurred (traceId: ${errorJson.traceId}). Check server logs for details.`;
            }
          } else {
            const errorText = await response.text();
            if (errorText && errorText.trim()) {
              errorMessage = errorText;
            }
          }
        } catch (e) {
          console.error("Error parsing error response:", e);
        }
        console.error(`Server error (${response.status}):`, errorMessage);
        throw new ServerFetchError(
          `Server error: ${response.status} ${response.statusText}. ${errorMessage}`,
          response
        );
    }
  }
}

export const serverFetch: typeof fetch = async (input, init) => {
  const session = await getServerSession<any, SessionWithTokens>(authOptions);
  const accessToken = session?.accessToken;

  return fetch(input, {
    ...init,
    headers: {
      ...init?.headers,
      ...(accessToken
        ? {
            Authorization: `Bearer ${accessToken}`,
            Accept:
              "application/json, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, multipart/form-data",
          }
        : {}),
    },
  });
};

type FetchParams = Parameters<typeof fetch>;

export async function serverFetchJson<T>(...args: FetchParams) {
  const url = String(args[0]);
  const t0 = performance.now();
  const response = await serverFetch(...args);
  const t1 = performance.now();
  if (process.env.NEXT_PUBLIC_DEBUG_FETCH_LOG === "true") {
    console.log(`[serverFetchJson] ${response.status} ${(t1 - t0).toFixed(1)}ms ${url}`);
  }
  if (response.ok) {
    if (response.status === 204) {
      return response.status as T;
    }

    return response.json() as T;
  } else {
    const errorText = await response.text().catch(() => "Unable to read error response");
    console.error("serverFetchJson - Error response:", response.status, errorText);
    switch (response.status) {
      case 401:
        signOutUser();
      default:
        throw new ServerFetchError(
          `Server error: ${response.status} ${response.statusText}. ${errorText}`,
          response,
        );
    }
  }
}

export async function serverFetchString<T>(...args: FetchParams) {
  const url = String(args[0]);
const t0 = performance.now();
const response = await serverFetch(...args);
const t1 = performance.now();
if (process.env.NEXT_PUBLIC_DEBUG_FETCH_LOG === "true") {
  console.log(`[serverFetchJson] ${response.status} ${(t1 - t0).toFixed(1)}ms ${url}`);
}

  if (response.ok) {
    return response.text() as T;
  } else {
    switch (response.status) {
      case 401:
        signOutUser();
      default:
        console.error(await response.text());
        throw new ServerFetchError(
          "Something went wrong fetching data in server.",
          response,
        );
    }
  }
}

export async function serverFetchBlob<T extends BlobResponse>(...args: FetchParams) {
  const response = await serverFetch(...args);

  if (response.ok) {
    const body = response.body;
    // console.log(body)
    // console.log(body?.tee()[0].getReader())

    const reader = body?.getReader();
    let finalUInt8Array = new Uint8Array();
    let done = false;

    while (!done) {
      const read = await reader?.read();

      if (read?.done) {
        done = true;
      } else {
        const tempUInt8Array = new Uint8Array(
          finalUInt8Array.length + read?.value.length!,
        );
        tempUInt8Array.set(finalUInt8Array);
        tempUInt8Array.set(read?.value!, finalUInt8Array.length);
        finalUInt8Array = new Uint8Array(tempUInt8Array.length!);
        finalUInt8Array.set(tempUInt8Array);

        // console.log("1", finalUInt8Array)
      }
    }

    // version 2 & return bodyRead
    // const bodyRead = reader?.read().then(function processText({ done, value }): any {
    //   // Result objects contain two properties:
    //   // done  - true if the stream has already given you all its data.
    //   // value - some data. Always undefined when done is true.
    //   if (done) {
    //     console.log("Stream complete");
    //     return { filename: response.headers.get("filename"), blobValue: finalUInt8Array } as T;;
    //   }

    //   // value for fetch streams is a Uint8Array
    //   finalUInt8Array = new Uint8Array(value.length)
    //   finalUInt8Array.set(value)

    //   console.log(finalUInt8Array)
    //   // Read some more, and call this function again
    //   return reader.read().then(processText);
    // })
    // const bodyValue = bodyRead?.value

    // const blob = await response.blob()
    // const blobText = await blob.text();
    // const blobType = await blob.type;

    // console.log(bodyReader)
    // console.log("2", finalUInt8Array)
    // console.log(bodyValue)
    return {
      filename: response.headers.get("filename"),
      blobValue: finalUInt8Array,
    } as T;
  } else {
    switch (response.status) {
      case 401:
        signOutUser();
      default:
        console.error(await response.text());
        throw Error("Something went wrong fetching data in server.");
    }
  }
}

export const signOutUser = () => {
  const headersList = headers();
  const referer = headersList.get("referer");
  redirect(
    `/logout${referer ? `?callbackUrl=${encodeURIComponent(referer)}` : ""}`,
  );
};
