import { cookies, headers } from "next/headers";
import { revalidatePath } from "next/cache";
import { NextRequest, NextResponse } from "next/server";
import {
  BagistoCreateUserOperation,
  BagistoProductInfo,
  BagistoSession,
  BagistoUser,
  ImageInfo,
} from "@/types/types";
import {
  HIDDEN_PRODUCT_TAG,
} from "../constants";
import { getServerSession } from "next-auth";
import {
  CUSTOMER_LOGOUT,
  CUSTOMER_REGISTRATION,
  FORGET_PASSWORD,
} from "@/graphql/customer/mutations";
import { print, DocumentNode } from "graphql";
import { 
  GRAPHQL_URL, 
  REST_API_URL,
} from "@/utils/constants/server";
import { 
  GUEST_CART_TOKEN,
  IS_GUEST,
  CURRENT_CURRENCY,
  CURRENT_LOCAL,
  CURRENT_CHANNEL,
} from "@/utils/constants";
import {
  GET_FOOTER,
  GET_THEME_CUSTOMIZATION,
  PAGE_BY_URL_KEY,
} from "@/graphql";
import { SUBSCRIBE_TO_NEWSLETTER } from "@/graphql/theme/mutations";
import { cachedGraphQLRequest } from "@/utils/hooks/useCache";
import { authOptions } from "@utils/auth";
import { RegisterInputs } from "@components/customer/RegistrationForm";
import {
  GetFooterResponse,
  ThemeCustomizationResult,
  ThemeCustomizationResponse,
  PageData,
} from "@/types/theme/theme-customization";
import { decodeJWT } from "@/utils/jwt-cookie";
import {FetchGraphqlResult} from "@/types/graphqlFetch/type";


type ExtractVariables<T> = T extends { variables: object }
  ? T["variables"]
  : never;

interface PageByUrlKeyResponse {
  pageByUrlKeypages?: PageData[];
}
const STOREFRONT_KEY = process.env.NEXT_PUBLIC_BAGISTO_STOREFRONT_KEY;
async function getBaseHeader() {
    /** 
     * Headers:
     *   X-LOCALE   — locale code, e.g. "en", "fr", "ar"
     *   X-CHANNEL  — channel code, e.g. "default"
     *   X-CURRENCY — currency code, e.g. "USD", "EUR", "INR"
     * */
    
    if(!STOREFRONT_KEY) {
        throw new Error("STOREFRONT_KEY must be configed!");
    }
    const cookieStore = await cookies();
    const xCurrency = cookieStore.get(CURRENT_CURRENCY)?.value; // 在proxy.ts中已经设置过值了
    const xLocal = cookieStore.get(CURRENT_LOCAL)?.value; // 在proxy.ts中已经设置过值了
    const xChannel = cookieStore.get(CURRENT_CHANNEL)?.value; // 在proxy.ts中已经设置过值了
    const baseHeaders: Record<string, string> = {
      "Content-Type": "application/json",
      "X-STOREFRONT-KEY": STOREFRONT_KEY,
      
    };
    if(xCurrency) {
      baseHeaders["X-Currency"] = xCurrency;
    }
    if(xLocal) {
      baseHeaders["X-Local"] = xLocal;
    }
    if(xChannel) {
      baseHeaders["X-Channel"] = xChannel;
    }
    return baseHeaders;
}


// rest api fetch
export async function restApiFetch<T>({
  api,
  method,
  cache = "force-cache",
  headers,
  tags,
  variables,
  isCookies = true,
  guestToken,
  revalidate = 60,
}: {
  api: string;
  method: "POST" | "GET" | "PUT" | "DELETE";
  cache?: RequestCache;
  headers?: HeadersInit | Record<string, string>;
  tags?: string[];
  variables?: ExtractVariables<T>;
  isCookies?: boolean;
  guestToken?: string;
  revalidate?: number;
}): Promise<{ status: number; body: T } | never> {
  try {
    const apiUrl = api.startsWith("http") ? api : `${REST_API_URL}${api}`;
    let accessToken: string | undefined = undefined;

    if (isCookies) {
      const sessions = (await getServerSession(
        authOptions,
      )) as BagistoSession | null;
      accessToken = sessions?.user?.accessToken;
    }

    const headerRes = await getBaseHeader();
    const baseHeaders: Record<string, string> = {...headerRes};

    if (accessToken) {
      baseHeaders.Authorization = `Bearer ${accessToken}`;
    } else if (guestToken) {
      baseHeaders.Authorization = `Bearer ${guestToken}`;
    }


    if (isCookies && headers) {
      if (headers instanceof Headers) {
        headers.forEach((value, key) => (baseHeaders[key] = value));
      } else {
        Object.assign(baseHeaders, headers);
      }
    }

    const param: RequestInit = {
      method: method,
      headers: baseHeaders,
      cache,
      next: {
        revalidate: cache === "no-store" ? 0 : revalidate || 60,
        ...(tags && { tags }),
      },
    };
    if(variables) {
      param.body = JSON.stringify({...variables});
    }
console.log('restApiFetch --- baseHeaders:', baseHeaders,apiUrl, param)
    const result = await fetch(apiUrl, param);
    console.log('restApiFetch --- result:', result)
    const body = await result.json();

    if (body.errors) throw body.errors[0];

    return { status: result.status, body };
  } catch (e) {
    throw e;
  }
}

// graphql fetch use for server side
export async function serverGraphqlFetch<
  TData,
  TVariables = Record<string, never>
>({
  cache = "no-store",
  headers,
  query,
  tags,
  variables,
  revalidate = 0,
  operationName = ''
}: {
  cache?: RequestCache;
  headers?: HeadersInit | Record<string, string>;
  query: string | DocumentNode;
  tags?: string[];
  variables?: TVariables;
  revalidate?: number;
  operationName?: string;
}): Promise<FetchGraphqlResult<TData>> {
  try {
    const queryString = typeof query === "string" ? query : print(query);

    let accessToken: string | undefined = undefined;
    let guestToken: string | undefined = undefined;

    const cookieStore = await cookies();
    
    const isGuest = cookieStore.get(IS_GUEST)?.value;
    const gt =  cookieStore.get(GUEST_CART_TOKEN)?.value;
    if(isGuest === 'false') {
      // 登录用户
      const sessions = (await getServerSession(
        authOptions,
      )) as BagistoSession | null;
      accessToken = sessions?.user?.accessToken;
    } else if(gt) {
        // 游客
        const jwtRes = decodeJWT<{
            sessionToken: string;
            cartId: number;
            isGuest: boolean;
        }>(gt, true);
        guestToken = jwtRes?.sessionToken || '';
    }

    const headerRes = await getBaseHeader();
    const baseHeaders: Record<string, string> = {...headerRes};

    if (accessToken) {
      baseHeaders['Authorization'] = `Bearer ${accessToken}`;
    } else if (guestToken) {
      baseHeaders['Authorization'] = `Bearer ${guestToken}`;
    }

    if (headers) {
      if (headers instanceof Headers) {
        headers.forEach((value, key) => (baseHeaders[key] = value));
      } else {
        Object.assign(baseHeaders, headers);
      }
    }
    console.log('serverGraphqlFetch --- baseHeaders:', baseHeaders);
    const result = await fetch(GRAPHQL_URL, {
      method: "POST",
      headers: baseHeaders,
      body: JSON.stringify({
        query: queryString,
        ...(variables && { variables }),
      }),
      cache,
      next: {
        revalidate: cache === "no-store" ? 0 : revalidate || 60,
        ...(tags && { tags }),
      },
    });

    const body = await result.json();
    console.log('serverGraphqlFetch --- body:',body);
    if (body.errors) {
      if(operationName === 'GetCartItem' && body.errors[0].message === 'Cart not found') {
        return { status: result.status, data: body.data, error: null };
      } else {
        return { status: result.status, data: body.data, error: body.errors[0] };
      }
    }
    return { status: result.status, data: body.data,error: null };
  } catch (e) {
    throw e;
  }
}

// graphql fetch use for route handler
export async function bagistoFetch<T>({
  cache = "force-cache",
  headers,
  query,
  tags,
  variables,
  isCookies = true,
  guestToken,
  revalidate = 60,
  operationName = ''
}: {
  cache?: RequestCache;
  headers?: HeadersInit | Record<string, string>;
  query: string | DocumentNode;
  tags?: string[];
  variables?: ExtractVariables<T>;
  isCookies?: boolean;
  guestToken?: string;
  revalidate?: number;
  operationName?: string;
}): Promise<{ status: number; body: T } | never> {
  try {
    const queryString =
      typeof query === "string" ? query : print(query);


    let accessToken: string | undefined = undefined;

    if (isCookies) {

      const sessions = (await getServerSession(
        authOptions,
      )) as BagistoSession | null;
      accessToken = sessions?.user?.accessToken;
    }

    const headerRes = await getBaseHeader();
    const baseHeaders: Record<string, string> = {...headerRes};

    if (accessToken) {
      baseHeaders.Authorization = `Bearer ${accessToken}`;
    } else if (guestToken) {
      baseHeaders.Authorization = `Bearer ${guestToken}`;
    }

    

    if (isCookies && headers) {
      if (headers instanceof Headers) {
        headers.forEach((value, key) => (baseHeaders[key] = value));
      } else {
        Object.assign(baseHeaders, headers);
      }
    }
    // let cc = await cookies();
    // console.log('bagistoFetch --- url:', GRAPHQL_URL,isCookies);
    // console.log('bagistoFetch --- queryString:', queryString);
    console.log('bagistoFetch --- baseHeaders:', baseHeaders);
    // console.log('bagistoFetch --- cookies:', cc.getAll());
    // console.log('bagistoFetch --- variables:', variables);
    const result = await fetch(GRAPHQL_URL, {
      method: "POST",
      headers: baseHeaders,
      body: JSON.stringify({
        query: queryString,
        ...(variables && { variables }),
      }),
      cache,
      next: {
        revalidate: cache === "no-store" ? 0 : revalidate || 60,
        ...(tags && { tags }),
      },
    });

    const body = await result.json();
    console.log('bagistoFetch --- body:',body);
    if (body.errors) {
      if(operationName === 'GetCartItem' && body.errors[0].message === 'Cart not found') {
        return { status: result.status, body };
      }
      throw body.errors[0]
    }
    return { status: result.status, body };
  } catch (e) {
    throw e;
  }
}
/*
export async function bagistoFetchNoSession<T>({
  query,
  tags,
  variables,
  headers,
  cache = "force-cache",
  revalidate = 60,
}: {
  query: string;
  tags?: string[];
  variables?: ExtractVariables<T>;
  headers?: HeadersInit | Record<string, string>;
  cache?: RequestCache;
  isCookies?: boolean;
  revalidate?: number;
}): Promise<{ status: number; body: T } | never> {
  try {
    const result = await fetch(GRAPHQL_URL, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-STOREFRONT-KEY": STOREFRONT_KEY,
        "x-locale": "en",
        "x-currency": "USD",
        ...headers,
      },
      body: JSON.stringify({
        ...(query && { query }),
        ...(variables && { variables }),
      }),
      cache,
      next: {
        revalidate: cache === "no-store" ? 0 : revalidate || 60,
        ...(tags && { tags }),
      },
    });

    const body = await result.json();

    if (body.errors) {
      throw body.errors[0];
    }

    return {
      status: result.status,
      body,
    };
  } catch (e) {
    throw { error: e, query };
  }
}
*/
export const removeEdgesAndNodes = <T>(array: Array<T>) => {
  return array?.map((edge) => edge);
};

const reshapeImages = (images: Array<ImageInfo>, productTitle: string) => {
  const flattened = removeEdgesAndNodes(images);

  return flattened.map((image) => {
    const filename = image?.url.match(/.*\/(.*)\..*/)?.[1];

    return {
      ...image,
      altText: image?.altText || `${productTitle} - ${filename}`,
    };
  });
};

const reshapeProduct = (
  product: BagistoProductInfo,
  filterHiddenProducts: boolean = true,
) => {
  if (
    !product ||
    (filterHiddenProducts && product.tags?.includes(HIDDEN_PRODUCT_TAG))
  ) {
    return undefined;
  }

  const { images, variants, ...rest } = product;

  return {
    ...rest,
    images: reshapeImages(images, product.title),
    variants: removeEdgesAndNodes(variants),
  };
};

export const reshapeProducts = (products: BagistoProductInfo[]) => {
  const reshapedProducts = [];

  for (const product of products) {
    if (product) {
      const reshapedProduct = reshapeProduct(product);

      if (reshapedProduct) {
        reshapedProducts.push(reshapedProduct);
      }
    }
  }

  return reshapedProducts;
};

export async function createUserToLogin(
  input: RegisterInputs,
): Promise<BagistoUser> {
  try {
    const { passwordConfirmation, ...userInput } = input;
    const res = await bagistoFetch<BagistoCreateUserOperation>({
      query: CUSTOMER_REGISTRATION,
      variables: {
        input: {
          ...userInput,
          confirmPassword: passwordConfirmation,
          status: "1",
          isVerified: "1",
          isSuspended: "0",
          subscribedToNewsLetter: true,
        },
      },
      cache: "no-store",
      revalidate: 3600,
    });

    return res.body.data.createCustomer.customer;
  } catch (error: any) {
    throw new Error(error?.message || "Registration failed");
  }
}

export async function logoutUser() {
  try {
    const session = await getServerSession(authOptions);
    const token = session?.user?.accessToken;

    if (!token) {
      return {
        success: false,
        message: "User token missing",
      };
    }
    /**
     * @todo CUSTOMER_LOGOUT 接口会报错，待修复（确定是php后端接口报错）
     */
    const res = await bagistoFetch<{
      data: { createLogout: { logout: { success: boolean; message: string } } };
      variables: { input: { token: string } };
    }>({
      query: CUSTOMER_LOGOUT,
      isCookies: true,
      revalidate: 3600,
    });

    const success = res?.body?.data?.createLogout?.logout?.success ?? false;

    const message =
      res?.body?.data?.createLogout?.logout?.message ?? "Logout executed";




    return {
      success,
      message,
    };
  } catch (error: unknown) {
    return {
      success: false,
      message: error instanceof Error ? error.message : "Something went wrong",
    };
  }
}

export async function recoverUserLogin(
  input: Record<string, unknown>,
): Promise<unknown> {
  try {
    return await bagistoFetch<{
      data: unknown;
      variables: Record<string, unknown>;
    }>({
      query: FORGET_PASSWORD,
      variables: {
        ...input,
      },
      cache: "no-store",
      revalidate: 3600,
    });
  } catch (error) {
    return error;
  }
}

export async function subscribeUser(
  input: Record<string, unknown>,
): Promise<unknown> {
  try {
    return await bagistoFetch<{
      data: unknown;
      variables: Record<string, unknown>;
    }>({
      query: SUBSCRIBE_TO_NEWSLETTER,
      variables: {
        ...input,
      },
      cache: "no-store",
    });
  } catch (error) {
    return error;
  }
}

export async function getThemeCustomization(): Promise<ThemeCustomizationResult> {
  try {
    const [{data: footerRes}, {data:servicesRes}] = await Promise.all([
      cachedGraphQLRequest<GetFooterResponse>("static", GET_FOOTER, {
        type: "footer_links",
      }),
      cachedGraphQLRequest<GetFooterResponse>("static", GET_FOOTER, {
        type: "services_content",
      }),
    ]);

    return {
      footer_links: footerRes,
      services_content: servicesRes,
    };
  } catch (err) {
    console.error("ThemeCustomization Error:", err);
  }

  return {
    footer_links: null,
    services_content: null,
  };
}

export async function revalidate(req: NextRequest): Promise<NextResponse> {
  const collectionWebhooks = [
    "collections/create",
    "collections/delete",
    "collections/update",
  ];
  const productWebhooks = [
    "products/create",
    "products/delete",
    "products/update",
  ];
  const topic = (await headers()).get("x-bagisto-topic") || "unknown";
  const secret = req.nextUrl.searchParams.get("secret");
  const isCollectionUpdate = collectionWebhooks.includes(topic);
  const isProductUpdate = productWebhooks.includes(topic);

  if (!secret || secret !== process.env.BAGISTO_REVALIDATION_SECRET) {
    return NextResponse.json({ status: 200 });
  }

  if (!isCollectionUpdate && !isProductUpdate) {
    return NextResponse.json({ status: 200, message: "No action needed" });
  }

  if (isProductUpdate) {
    revalidatePath("/", "layout");
  } else if (isCollectionUpdate) {
    revalidatePath("/", "layout");
  }

  return NextResponse.json({
    status: 200,
    revalidated: true,
    topic,
    now: Date.now(),
  });
}

export async function getHomePageData(): Promise<ThemeCustomizationResponse> {
  const res = await bagistoFetch<{
    data: ThemeCustomizationResponse;
    variables: { first: number };
  }>({
    query: GET_THEME_CUSTOMIZATION,
    variables: { first: 20 },
    tags: ["theme-customization"],
    revalidate: 60,
  });

  return res.body.data;
}

export async function getPage(input: { urlKey: string }): Promise<PageData[]> {
  const res = await bagistoFetch<{
    data: PageByUrlKeyResponse;
    variables: { pageByUrlKey: string };
  }>({
    query: PAGE_BY_URL_KEY,
    cache: "no-store",
    isCookies: false,
    variables: { pageByUrlKey: input.urlKey },
  });

  return res.body.data?.pageByUrlKeypages || [];
}
