import type { MoonwellClient } from "../../client/createMoonwellClient.js";
import {
  type SnapshotPeriod,
  applyGranularity,
  calculateTimeRange,
  getEnvironmentFromArgs,
  toApiGranularity,
} from "../../common/index.js";
import type { NetworkParameterType } from "../../common/types.js";
import type { Chain } from "../../environments/index.js";
import type { StakingSnapshot } from "../../types/staking.js";
import { postWithRetry } from "../axiosWithRetry.js";
import {
  DEFAULT_LUNAR_TIMEOUT_MS,
  createLunarIndexerClient,
} from "../lunar-indexer-client.js";
import { transformStakingSnapshots } from "../lunar-indexer-transformers.js";

export type GetStakingSnapshotsParameters<
  environments,
  network extends Chain | undefined,
> = NetworkParameterType<environments, network> & {
  period?: SnapshotPeriod;
  startTime?: number;
  endTime?: number;
};

export type GetStakingSnapshotsReturnType = Promise<StakingSnapshot[]>;

export async function getStakingSnapshots<
  environments,
  Network extends Chain | undefined,
>(
  client: MoonwellClient,
  args?: GetStakingSnapshotsParameters<environments, Network>,
): GetStakingSnapshotsReturnType {
  const environment = getEnvironmentFromArgs(client, args);

  if (!environment) {
    return [];
  }

  const {
    period,
    startTime: customStartTime,
    endTime: customEndTime,
  } = (args ?? {}) as GetStakingSnapshotsParameters<environments, undefined>;

  if (!environment.lunarIndexerUrl) {
    const { startTime } = calculateTimeRange(
      period,
      customStartTime,
      customEndTime,
    );
    return environment.indexerUrl
      ? fetchStakingSnapshotsFromPonder(
          environment.chainId,
          environment.indexerUrl,
          startTime,
        )
      : [];
  }

  try {
    return await fetchStakingSnapshotsFromLunar(
      environment.chainId,
      environment.lunarIndexerUrl,
      period,
      customStartTime,
      customEndTime,
    );
  } catch (error) {
    console.warn(
      `[getStakingSnapshots] Lunar Indexer failed for chain ${environment.chainId}:`,
      error,
    );
    environment.onError?.(error, {
      source: "staking-snapshots",
      chainId: environment.chainId,
    });
    return [];
  }
}

async function fetchStakingSnapshotsFromLunar(
  chainId: number,
  lunarIndexerUrl: string,
  period?: SnapshotPeriod,
  customStartTime?: number,
  customEndTime?: number,
): Promise<StakingSnapshot[]> {
  const lunarClient = createLunarIndexerClient({
    baseUrl: lunarIndexerUrl,
    timeout: DEFAULT_LUNAR_TIMEOUT_MS,
  });

  const { startTime, endTime, granularity } = calculateTimeRange(
    period,
    customStartTime,
    customEndTime,
  );

  const allSnapshots: StakingSnapshot[] = [];
  let cursor: string | null = null;
  const MAX_PAGES = 100;
  let page = 0;

  do {
    const response = await lunarClient.getStakingSnapshots(chainId, {
      limit: 1000,
      granularity: toApiGranularity(granularity),
      startTime,
      endTime,
      ...(cursor && { cursor }),
    });

    allSnapshots.push(...transformStakingSnapshots(response.results));
    cursor = response.nextCursor;
    page++;
  } while (cursor !== null && page < MAX_PAGES);

  allSnapshots.sort((a, b) => a.timestamp - b.timestamp);
  return applyGranularity(allSnapshots, granularity);
}

async function fetchStakingSnapshotsFromPonder(
  chainId: number,
  indexerUrl: string,
  startTime?: number,
): Promise<StakingSnapshot[]> {
  try {
    const response = await postWithRetry<{
      data: {
        stakingDailySnapshots: {
          items: StakingSnapshot[];
        };
      };
    }>(indexerUrl, {
      query: `
          query {
            stakingDailySnapshots(
              limit: 365,
              orderBy: "timestamp"
              orderDirection: "desc"
              where: {chainId: ${chainId}}
            ) {
              items {
                chainId
                totalStaked
                totalStakedUSD
                timestamp
              }
            }
          }
        `,
    });

    if (response.status === 200 && response.data?.data?.stakingDailySnapshots) {
      const items = response.data.data.stakingDailySnapshots.items;
      return startTime
        ? items.filter((item) => item.timestamp >= startTime)
        : items;
    } else {
      return [];
    }
  } catch (ex) {
    console.error("An error occured while fetching getStakingSnapshots...", ex);
    return [];
  }
}
