import { TE_CONFIG } from '../shared/config';
import { decodeTEData } from '../shared/decode';
import type { TeMarketDataFetchPayload, TeMkCdnSerie } from '../shared/types';
import type { TeHandler } from '../te.handler';

const CATEGORY_MAP: Record<string, string> = {
  ind: 'index',
  cur: 'currency',
  gov: 'bond',
  com: 'commodity',
};

/** OHLC point: [epoch, close, pctChange, absChange, open, high, low, close] */
interface MkDataPoint {
  t: number;
  o: number;
  h: number;
  l: number;
  c: number;
  pc: number | null;
  ac: number | null;
}

async function fetchMkSerie(
  handler: TeHandler,
  cdnBase: string,
  params: Record<string, string>,
): Promise<TeMkCdnSerie | null> {
  const url = new URL(cdnBase);
  for (const [k, v] of Object.entries(params)) {
    url.searchParams.set(k, v);
  }

  const response = await handler.http.get(url.toString(), {
    proxy: handler.proxy?.getProxy(),
    headers: { 'Accept': '*/*', 'User-Agent': TE_CONFIG.USER_AGENT },
  });

  if (!response.ok) {
    throw new Error(`CDN HTTP ${response.status} for ${url.toString()}`);
  }

  const rawText = (await response.text()).replaceAll('"', '');
  if (!rawText.length) return null;

  const decoded = decodeTEData<{ series: TeMkCdnSerie[] }>(rawText, TE_CONFIG.KEY);
  return decoded?.series?.[0] ?? null;
}

function toPoints(data: any[]): MkDataPoint[] {
  return data.map(([t, _c, pc, ac, o, h, l, c]: number[]) => ({ t, o, h, l, c, pc, ac }));
}

function mergePoints(existing: MkDataPoint[], incoming: MkDataPoint[]): MkDataPoint[] {
  const map = new Map(existing.map((p) => [p.t, p]));
  for (const p of incoming) {
    map.set(p.t, p);
  }
  return Array.from(map.values()).sort((a, b) => a.t - b.t);
}

export async function marketDataFetch(
  this: TeHandler,
  payload: TeMarketDataFetchPayload,
): Promise<{ success: boolean; recordCount: number }> {
  const { logger, mongodb } = this;
  const { teSymbol, symbolType, url, isInitialFetch } = payload;

  const teDoc = await mongodb?.findOne('teUrls', { url });
  if (!teDoc?.teChartsDatasource || !teDoc?.teChartToken || !teDoc?.teLastUpdate) {
    logger.error(`Missing CDN fields for ${teSymbol} (${url})`);
    return { success: false, recordCount: 0 };
  }

  const cdnBase = `${teDoc.teChartsDatasource}/markets/${encodeURIComponent(teSymbol.toLowerCase())}:${symbolType}`;
  const baseParams = {
    ohlc: '1',
    v: teDoc.teLastUpdate,
    key: teDoc.teChartToken,
  };
  const category = CATEGORY_MAP[symbolType] || symbolType;

  if (isInitialFetch) {
    logger.info(`Fetching market data: ${teSymbol}:${symbolType} (initial — daily + monthly)`);

    const [dailySerie, monthlySerie] = await Promise.all([
      fetchMkSerie(this, cdnBase, { ...baseParams, interval: '1d', span: '11y' }),
      fetchMkSerie(this, cdnBase, { ...baseParams, span: 'max' }),
    ]);

    const daily = dailySerie?.data?.length ? toPoints(dailySerie.data) : [];
    const monthly = monthlySerie?.data?.length ? toPoints(monthlySerie.data) : [];
    const serie = dailySerie || monthlySerie;

    if (!daily.length && !monthly.length) {
      logger.warn(`No data points decoded for ${teSymbol}:${symbolType}`);
      await mongodb?.updateOne('teUrls', { url }, {
        $set: { lastDataFetch: new Date(), lastDataFetchUpdate: teDoc.teLastUpdate, lastDataFetchCount: 0 },
      });
      return { success: true, recordCount: 0 };
    }

    logger.info(`Storing ${daily.length} daily + ${monthly.length} monthly OHLC points for ${teSymbol} (${category})`);

    await mongodb?.updateOne('teMarketData', { teSymbol }, {
      $set: {
        teSymbol,
        name: serie?.name,
        fullName: serie?.full_name,
        unit: serie?.unit,
        type: serie?.type,
        category,
        symbolType,
        ticker: serie?.ticker,
        sector: serie?.sector,
        industry: serie?.industry,
        frequency: serie?.frequency,
        daily,
        monthly,
        lastUpdated: new Date(),
      },
      $setOnInsert: { createdAt: new Date() },
    }, { upsert: true });

    const totalPoints = daily.length + monthly.length;

    await mongodb?.updateOne('teUrls', { url }, {
      $set: { lastDataFetch: new Date(), lastDataFetchUpdate: teDoc.teLastUpdate, lastDataFetchCount: totalPoints },
    });

    return { success: true, recordCount: totalPoints };
  }

  // Update: fetch recent daily OHLC, merge into existing
  logger.info(`Fetching market data: ${teSymbol}:${symbolType} (update)`);

  const serie = await fetchMkSerie(this, cdnBase, { ...baseParams, interval: '1d' });

  if (!serie?.data?.length) {
    logger.warn(`No update data for ${teSymbol}:${symbolType}`);
    return { success: true, recordCount: 0 };
  }

  const incoming = toPoints(serie.data);
  const existing = await mongodb?.findOne('teMarketData', { teSymbol });

  const merged = existing?.daily ? mergePoints(existing.daily, incoming) : incoming;

  await mongodb?.updateOne('teMarketData', { teSymbol }, {
    $set: { daily: merged, lastUpdated: new Date() },
  });

  await mongodb?.updateOne('teUrls', { url }, {
    $set: { lastDataFetch: new Date(), lastDataFetchUpdate: teDoc.teLastUpdate, lastDataFetchCount: merged.length },
  });

  return { success: true, recordCount: merged.length };
}
