import { type CoreResponse } from '../../core/index.js';
import type { ICoreResponse } from '../../core_base/types.js';
import {
  type TPaginationConfig,
  PaginationStrategy,
  type TPaginatedResponse,
} from '../types/pagination.js';

/**
 * Creates a paginated response from a regular response
 */
export async function createPaginatedResponse<T>(
  response: ICoreResponse<any>,
  paginationConfig: TPaginationConfig,
  queryParams: Record<string, string>,
  fetchNextPage: (
    params: Record<string, string>,
  ) => Promise<TPaginatedResponse<T>>,
): Promise<TPaginatedResponse<T>> {
  // Parse response body first
  const body = (await response.json()) as any;

  // Default to response.body for items if response is JSON
  let items: T[] = Array.isArray(body)
    ? body
    : body?.items || body?.data || body?.results || [];

  let hasNextPage = false;
  let nextPageParams: Record<string, string> = {};

  // Determine if there's a next page based on pagination strategy
  switch (paginationConfig.strategy) {
    case PaginationStrategy.OFFSET: {
      const config = paginationConfig;
      const currentPage = parseInt(
        queryParams[config.pageParam || 'page'] ||
          String(config.startPage || 1),
      );
      const limit = parseInt(
        queryParams[config.limitParam || 'limit'] ||
          String(config.pageSize || 20),
      );
      const total = getValueByPath(body, config.totalPath || 'total') || 0;

      hasNextPage = currentPage * limit < total;

      if (hasNextPage) {
        nextPageParams = {
          ...queryParams,
          [config.pageParam || 'page']: String(currentPage + 1),
        };
      }
      break;
    }

    case PaginationStrategy.CURSOR: {
      const config = paginationConfig;
      const nextCursor = getValueByPath(
        body,
        config.cursorPath || 'nextCursor',
      );
      const hasMore = getValueByPath(body, config.hasMorePath || 'hasMore');

      hasNextPage = !!nextCursor || !!hasMore;

      if (hasNextPage && nextCursor) {
        nextPageParams = {
          ...queryParams,
          [config.cursorParam || 'cursor']: nextCursor,
        };
      }
      break;
    }

    case PaginationStrategy.LINK_HEADER: {
      const linkHeader = response.headers['link'] || '';
      // Handle both string and string[] types for the link header
      const headerValue = Array.isArray(linkHeader)
        ? linkHeader[0]
        : linkHeader;
      const links = parseLinkHeader(headerValue);

      hasNextPage = !!links.next;

      if (hasNextPage && links.next) {
        // Extract query parameters from next link URL
        const url = new URL(links.next);
        nextPageParams = {};

        url.searchParams.forEach((value, key) => {
          nextPageParams[key] = value;
        });
      }
      break;
    }

    case PaginationStrategy.CUSTOM: {
      const config = paginationConfig;
      hasNextPage = config.hasNextPage(response);

      if (hasNextPage) {
        nextPageParams = config.getNextPageParams(response, queryParams);
      }
      break;
    }
  }

  // Create a function to fetch the next page
  const getNextPage = async (): Promise<TPaginatedResponse<T>> => {
    if (!hasNextPage) {
      throw new Error('No more pages available');
    }

    return fetchNextPage(nextPageParams);
  };

  // Create a function to fetch all remaining pages
  const getAllPages = async (): Promise<T[]> => {
    const allItems = [...items];
    let currentPage: TPaginatedResponse<T> = {
      items,
      hasNextPage,
      getNextPage,
      getAllPages,
      response,
    };

    while (currentPage.hasNextPage) {
      try {
        currentPage = await currentPage.getNextPage();
        allItems.push(...currentPage.items);
      } catch (error) {
        break;
      }
    }

    return allItems;
  };

  return {
    items,
    hasNextPage,
    getNextPage,
    getAllPages,
    response,
  };
}

/**
 * Parse Link header for pagination
 * Link: <https://api.example.com/users?page=2>; rel="next", <https://api.example.com/users?page=5>; rel="last"
 */
export function parseLinkHeader(header: string): Record<string, string> {
  const links: Record<string, string> = {};

  if (!header) {
    return links;
  }

  // Split parts by comma
  const parts = header.split(',');

  // Parse each part into a name:value pair
  for (const part of parts) {
    const section = part.split(';');
    if (section.length < 2) {
      continue;
    }

    const url = section[0].replace(/<(.*)>/, '$1').trim();
    const name = section[1].replace(/rel="(.*)"/, '$1').trim();

    links[name] = url;
  }

  return links;
}

/**
 * Get a nested value from an object using dot notation path
 * e.g., getValueByPath(obj, "data.pagination.nextCursor")
 */
export function getValueByPath(obj: any, path?: string): any {
  if (!path || !obj) {
    return undefined;
  }

  const keys = path.split('.');
  let current = obj;

  for (const key of keys) {
    if (
      current === null ||
      current === undefined ||
      typeof current !== 'object'
    ) {
      return undefined;
    }
    current = current[key];
  }

  return current;
}
