import { getChain, type ChainId } from "./chains.ts";
import { retryTime, type RawPage } from "./liquidity.ts";

export type FeedEnvironment = { COINGECKO_PRO_API_KEY?: string; COINGECKO_DEMO_API_KEY?: string; COINGECKO_API_PLAN?: string };
export function providerConfig(env: FeedEnvironment = {}): { id: string; name: string; root: string; headers: Record<string, string>; batchSize: number; interval: number } {
  if (env.COINGECKO_PRO_API_KEY) {
    const analyst = env.COINGECKO_API_PLAN === "analyst";
    return { id: analyst ? "coingecko-analyst" : "coingecko-basic", name: "CoinGecko", root: "https://pro-api.coingecko.com/api/v3/onchain", headers: { "x-cg-pro-api-key": env.COINGECKO_PRO_API_KEY }, batchSize: analyst ? 50 : 30, interval: analyst ? 10_000 : 30_000 };
  }
  if (env.COINGECKO_DEMO_API_KEY) return { id: "coingecko-demo", name: "CoinGecko", root: "https://api.coingecko.com/api/v3/onchain", headers: { "x-cg-demo-api-key": env.COINGECKO_DEMO_API_KEY }, batchSize: 30, interval: 300_000 };
  return { id: "geckoterminal", name: "GeckoTerminal", root: "https://api.geckoterminal.com/api/v2", headers: {}, batchSize: 30, interval: 20_000 };
}
export class ProviderError extends Error {
  status: number;
  retryAt: number;
  constructor(message: string, status: number, retryAt = 0) { super(message); this.status = status; this.retryAt = retryAt; }
}
export function validRawPage(value: unknown): value is RawPage {
  if (!value || typeof value !== "object") return false;
  const page = value as RawPage;
  if (!Array.isArray(page.data) || !page.data.length || !Array.isArray(page.included)) return false;
  const tokens = new Set(page.included.filter(x => x.type === "token" && x.attributes?.symbol).map(x => x.id));
  return page.data.every(p => typeof p.id === "string" && typeof p.attributes?.address === "string" &&
    typeof p.attributes.name === "string" && tokens.has(p.relationships?.base_token?.data?.id) &&
    tokens.has(p.relationships?.quote_token?.data?.id) && typeof p.relationships?.dex?.data?.id === "string");
}
export type CollectionTask = { page: number } | { addresses: string[] };
export async function fetchPoolBatch(chainId: ChainId, task: CollectionTask, env: FeedEnvironment = {}): Promise<RawPage> {
  const chain = getChain(chainId)!;
  const provider = providerConfig(env);
  const path = "page" in task ? `/networks/${chain.network}/pools` : `/networks/${chain.network}/pools/multi/${task.addresses.join(",")}`;
  const query = new URLSearchParams({ include: "base_token,quote_token,dex" });
  if ("page" in task) { query.set("page", String(task.page)); query.set("sort", chain.poolSort); }
  const response = await fetch(`${provider.root}${path}?${query}`, {
    headers: { accept: "application/json;version=20230203", ...provider.headers },
    signal: AbortSignal.timeout(8_000),
  });
  if (!response.ok) throw new ProviderError(`${provider.name}: HTTP ${response.status}`, response.status, response.status === 429 || response.status === 503 ? retryTime(response.headers.get("retry-after")) : 0);
  const page: unknown = await response.json();
  if (!validRawPage(page)) throw new ProviderError("Incomplete pool data", 502);
  const age = Math.max(0, Number(response.headers.get("age")) || 0);
  page.observedAt = new Date(Date.now() - age * 1000).toISOString();
  return page;
}
