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

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 const serverFetch: typeof fetch = async (input, init) => {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const session = await getServerSession<any, SessionWithTokens>(authOptions);
  const accessToken = session?.accessToken;

  // console.log("Access Token: ", accessToken)
  // console.log(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 response = await serverFetch(...args);
  if (response.ok) {
    return response.json() as T;
  } else {
    const errorText = await response.text()
    switch (response.status) {
      case 401:
        signOutUser();
      case 417:
        signOutUser();
      case 422:
        throw new ServerFetchError(
          JSON.parse(errorText).error,
          response
        );
      default:
        console.error(errorText);
        throw new ServerFetchError(
          "Something went wrong fetching data in server.",
          response
        );
    }
  }
}

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

  if (response.ok) {
    return response.status; // 204 No Content, e.g. for delete data
  } else {
    switch (response.status) {
      case 401:
        signOutUser();
      default:
        console.error(await response.text());
        throw Error("Something went wrong fetching data in server.");
    }
  }
}

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

  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>(...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();

      // version 1
      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)}` : ""}`,
  );
};

export const fetchUserAbilities = async () => {
  const session = await getServerSession(authOptions) as SessionWithTokens;
  return session?.abilities ?? [] as string[]
}

export const fetchUserStaff = async () => {
  const session = await getServerSession(authOptions) as SessionWithTokens;
  return session?.staff as SessionStaff
}