import { useEffect, useRef } from 'react';
import {
  createChart,
  createSeriesMarkers,
  CandlestickSeries,
  LineSeries,
  ColorType,
  LineStyle,
  type IChartApi,
  type CandlestickData,
  type LineData,
  type SeriesMarker,
  type UTCTimestamp,
} from 'lightweight-charts';
import type { CandleEntry, PaperRunPosition } from '../types/api';
import type { IndicatorSpec } from '../utils/extractSmaSpecs';

const INDICATOR_COLORS = ['#2962ff', '#ff6d00', '#6a1b9a', '#e53935', '#00897b', '#795548'];
const SUPERTREND_COLOR = '#e040fb';

function computeSma(candles: CandleEntry[], field: string, period: number): LineData[] {
  const result: LineData[] = [];
  const key = field as keyof CandleEntry;
  for (let i = period - 1; i < candles.length; i++) {
    let sum = 0;
    for (let j = i - period + 1; j <= i; j++) {
      sum += Number(candles[j][key]);
    }
    result.push({
      time: (new Date(candles[i].open_time).getTime() / 1000) as UTCTimestamp,
      value: sum / period,
    });
  }
  return result;
}

function computeEma(candles: CandleEntry[], field: string, period: number): LineData[] {
  const result: LineData[] = [];
  const key = field as keyof CandleEntry;
  const k = 2 / (period + 1);
  let ema = 0;
  let seeded = false;

  for (let i = 0; i < candles.length; i++) {
    const val = Number(candles[i][key]);
    if (!seeded) {
      if (i < period - 1) continue;
      // Seed with SMA of first `period` values
      let sum = 0;
      for (let j = i - period + 1; j <= i; j++) sum += Number(candles[j][key]);
      ema = sum / period;
      seeded = true;
    } else {
      ema = val * k + ema * (1 - k);
    }
    result.push({
      time: (new Date(candles[i].open_time).getTime() / 1000) as UTCTimestamp,
      value: ema,
    });
  }
  return result;
}

function computeBollinger(
  candles: CandleEntry[],
  field: string,
  period: number,
  multiplier: number,
): { upper: LineData[]; middle: LineData[]; lower: LineData[] } {
  const upper: LineData[] = [];
  const middle: LineData[] = [];
  const lower: LineData[] = [];
  const key = field as keyof CandleEntry;
  for (let i = period - 1; i < candles.length; i++) {
    const values: number[] = [];
    for (let j = i - period + 1; j <= i; j++) {
      values.push(Number(candles[j][key]));
    }
    const mean = values.reduce((a, b) => a + b, 0) / period;
    const variance = values.reduce((s, v) => s + (v - mean) ** 2, 0) / period;
    const std = Math.sqrt(variance);
    const time = (new Date(candles[i].open_time).getTime() / 1000) as UTCTimestamp;
    upper.push({ time, value: mean + multiplier * std });
    middle.push({ time, value: mean });
    lower.push({ time, value: mean - multiplier * std });
  }
  return { upper, middle, lower };
}

function computeSupertrend(candles: CandleEntry[], period: number, multiplier: number): LineData[] {
  if (candles.length < period + 1) return [];

  // True Range
  const tr: number[] = candles.map((c, i) => {
    if (i === 0) return c.high - c.low;
    return Math.max(
      c.high - c.low,
      Math.abs(c.high - candles[i - 1].close),
      Math.abs(c.low - candles[i - 1].close),
    );
  });

  // ATR via Wilder's smoothing (seed with SMA, then RMA)
  const atr: number[] = new Array(candles.length).fill(NaN);
  let sum = 0;
  for (let i = 0; i < period; i++) sum += tr[i];
  atr[period - 1] = sum / period;
  for (let i = period; i < candles.length; i++) {
    atr[i] = (atr[i - 1] * (period - 1) + tr[i]) / period;
  }

  const upperBand: number[] = new Array(candles.length).fill(NaN);
  const lowerBand: number[] = new Array(candles.length).fill(NaN);
  // trend: 1 = bearish (on upper band), -1 = bullish (on lower band)
  const trend: number[] = new Array(candles.length).fill(0);
  const result: LineData[] = [];

  for (let i = period - 1; i < candles.length; i++) {
    if (isNaN(atr[i])) continue;
    const hl2 = (candles[i].high + candles[i].low) / 2;
    const basicUpper = hl2 + multiplier * atr[i];
    const basicLower = hl2 - multiplier * atr[i];

    if (i === period - 1) {
      upperBand[i] = basicUpper;
      lowerBand[i] = basicLower;
      trend[i] = 1; // start bearish until we see a flip
    } else {
      // Upper band can only decrease; reset if prev close broke above it
      upperBand[i] = (basicUpper < upperBand[i - 1] || candles[i - 1].close > upperBand[i - 1])
        ? basicUpper : upperBand[i - 1];
      // Lower band can only increase; reset if prev close broke below it
      lowerBand[i] = (basicLower > lowerBand[i - 1] || candles[i - 1].close < lowerBand[i - 1])
        ? basicLower : lowerBand[i - 1];

      if (trend[i - 1] === -1 && candles[i].close < lowerBand[i]) {
        trend[i] = 1; // flip to bearish
      } else if (trend[i - 1] === 1 && candles[i].close > upperBand[i]) {
        trend[i] = -1; // flip to bullish
      } else {
        trend[i] = trend[i - 1];
      }
    }

    result.push({
      time: (new Date(candles[i].open_time).getTime() / 1000) as UTCTimestamp,
      value: trend[i] === 1 ? upperBand[i] : lowerBand[i],
    });
  }
  return result;
}

function buildMarkers(positions: PaperRunPosition[]): SeriesMarker<UTCTimestamp>[] {
  const markers: SeriesMarker<UTCTimestamp>[] = [];
  for (const pos of positions) {
    const entryTime = (new Date(pos.time_enter).getTime() / 1000) as UTCTimestamp;
    const exitTime = (new Date(pos.time_exit).getTime() / 1000) as UTCTimestamp;
    const entryPrice = parseFloat(pos.price_entry);
    const exitPrice = parseFloat(pos.price_exit);
    const profitable = parseFloat(pos.pnl) >= 0;

    markers.push({
      time: entryTime,
      position: 'atPriceBottom',
      shape: 'arrowUp',
      color: '#198754',
      price: entryPrice,
      size: 1,
    });
    markers.push({
      time: exitTime,
      position: 'atPriceTop',
      shape: 'arrowDown',
      color: profitable ? '#198754' : '#dc3545',
      price: exitPrice,
      size: 1,
    });
  }
  markers.sort((a, b) => (a.time as number) - (b.time as number));
  return markers;
}

interface Props {
  candles: CandleEntry[];
  interval: string;
  indicatorSpecs?: IndicatorSpec[];
  positions?: PaperRunPosition[];
}

export default function CandlestickChart({
  candles,
  interval,
  indicatorSpecs = [],
  positions = [],
}: Props) {
  const containerRef = useRef<HTMLDivElement>(null);
  const chartRef = useRef<IChartApi | null>(null);

  useEffect(() => {
    if (!containerRef.current || candles.length === 0) return;

    const chart = createChart(containerRef.current, {
      layout: {
        background: { type: ColorType.Solid, color: '#ffffff' },
        textColor: '#333',
        attributionLogo: false,
      },
      autoSize: true,
      height: 400,
      grid: {
        vertLines: { color: '#e1e1e1' },
        horzLines: { color: '#e1e1e1' },
      },
      timeScale: {
        timeVisible: true,
        secondsVisible: false,
      },
    });

    const candleSeries = chart.addSeries(CandlestickSeries, {
      upColor: '#198754',
      downColor: '#dc3545',
      borderDownColor: '#dc3545',
      borderUpColor: '#198754',
      wickDownColor: '#dc3545',
      wickUpColor: '#198754',
    });

    const data: CandlestickData[] = candles.map((c) => ({
      time: (new Date(c.open_time).getTime() / 1000) as UTCTimestamp,
      open: c.open,
      high: c.high,
      low: c.low,
      close: c.close,
    }));
    candleSeries.setData(data);

    if (positions.length > 0) {
      createSeriesMarkers(candleSeries, buildMarkers(positions));
    }

    // Render indicators — shared color index across SMA/EMA/Bollinger so colors don't repeat
    let colorIdx = 0;
    for (const spec of indicatorSpecs) {
      if (spec.kind === 'bollinger') {
        const { upper, middle, lower } = computeBollinger(candles, spec.field, spec.period, spec.multiplier);
        if (middle.length === 0) continue;
        const color = INDICATOR_COLORS[colorIdx % INDICATOR_COLORS.length];
        colorIdx++;
        const label = `BB(${spec.period},${spec.multiplier})`;
        chart.addSeries(LineSeries, {
          color,
          lineWidth: 1,
          lineStyle: LineStyle.Solid,
          priceLineVisible: false,
          lastValueVisible: true,
          title: `${label} Mid`,
        }).setData(middle);
        chart.addSeries(LineSeries, {
          color,
          lineWidth: 1,
          lineStyle: LineStyle.Dashed,
          priceLineVisible: false,
          lastValueVisible: false,
          title: `${label} Upper`,
        }).setData(upper);
        chart.addSeries(LineSeries, {
          color,
          lineWidth: 1,
          lineStyle: LineStyle.Dashed,
          priceLineVisible: false,
          lastValueVisible: false,
          title: `${label} Lower`,
        }).setData(lower);
        continue;
      }

      if (spec.kind === 'supertrend') {
        const stData = computeSupertrend(candles, spec.period, spec.multiplier);
        if (stData.length === 0) continue;
        chart.addSeries(LineSeries, {
          color: SUPERTREND_COLOR,
          lineWidth: 2,
          lineStyle: LineStyle.Solid,
          priceLineVisible: false,
          lastValueVisible: true,
          title: `ST(${spec.period},${spec.multiplier})`,
        }).setData(stData);
        continue;
      }

      const color = INDICATOR_COLORS[colorIdx % INDICATOR_COLORS.length];
      colorIdx++;

      if (spec.kind === 'sma') {
        const lineData = computeSma(candles, spec.field, spec.period);
        if (lineData.length === 0) continue;
        chart.addSeries(LineSeries, {
          color,
          lineWidth: 1,
          lineStyle: LineStyle.Solid,
          priceLineVisible: false,
          lastValueVisible: true,
          title: `SMA(${spec.period})`,
        }).setData(lineData);
      } else if (spec.kind === 'ema') {
        const lineData = computeEma(candles, spec.field, spec.period);
        if (lineData.length === 0) continue;
        chart.addSeries(LineSeries, {
          color,
          lineWidth: 1,
          lineStyle: LineStyle.Dashed,
          priceLineVisible: false,
          lastValueVisible: true,
          title: `EMA(${spec.period})`,
        }).setData(lineData);
      }
    }

    chart.timeScale().fitContent();
    chartRef.current = chart;

    return () => {
      chart.remove();
      chartRef.current = null;
    };
  }, [candles, interval, indicatorSpecs, positions]);

  return <div ref={containerRef} style={{ height: 400 }} />;
}
