import {CardPositionItem, TradeChart} from '..'
import {
    Backtest,
    BacktestClient,
    CandlesWithIndicatorsResponse,
    DataClient,
    GetCandlesWithIndicatorsRequest,
    IndicatorType,
    LightSignal,
    Position,
    SignalType
} from '../../../generated/ManagingApi'
import {CardPosition, CardText} from '../../mollecules'
import {useQuery} from '@tanstack/react-query'
import useApiUrlStore from '../../../app/store/apiStore'

interface IBacktestRowDetailsProps {
  backtest: Backtest;
}

const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
  backtest
}) => {
  const {apiUrl} = useApiUrlStore();
  const dataClient = new DataClient({}, apiUrl);
  const backtestClient = new BacktestClient({}, apiUrl);

  // Use TanStack Query to load full backtest data
  const {data: fullBacktestData, isLoading: isLoadingFullBacktest, error: fullBacktestError} = useQuery({
    queryKey: ['fullBacktest', backtest.id],
    queryFn: async (): Promise<Backtest> => {
      return await backtestClient.backtest_Backtest(backtest.id);
    },
    staleTime: 5 * 60 * 1000, // 5 minutes
    gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime)
  });

  // Use TanStack Query to load candles with indicators
  const {data: candlesData, isLoading: isLoadingCandles, error} = useQuery({
    queryKey: ['candlesWithIndicators', backtest.id, backtest.config?.scenario?.name],
    queryFn: async (): Promise<CandlesWithIndicatorsResponse> => {
      // Only fetch if no candles are present
      if (backtest.candles && backtest.candles.length > 0) {
        return {
          candles: backtest.candles,
          indicatorsValues: {} // Default empty object since Backtest doesn't have indicatorsValues
        };
      }

      const request: GetCandlesWithIndicatorsRequest = {
        ticker: backtest.config.ticker,
        startDate: backtest.startDate,
        endDate: backtest.endDate,
        timeframe: backtest.config.timeframe,
        scenario: backtest.config?.scenario ? {
          name: backtest.config.scenario.name || '',
          indicators: backtest.config.scenario.indicators?.map(indicator => ({
            name: indicator.name || '',
            type: indicator.type || IndicatorType.RsiDivergence,
            signalType: indicator.signalType || SignalType.Signal,
            minimumHistory: indicator.minimumHistory || 0,
            period: indicator.period,
            fastPeriods: indicator.fastPeriods,
            slowPeriods: indicator.slowPeriods,
            signalPeriods: indicator.signalPeriods,
            multiplier: indicator.multiplier,
            smoothPeriods: indicator.smoothPeriods,
            stochPeriods: indicator.stochPeriods,
            cyclePeriods: indicator.cyclePeriods
          })) || [],
          loopbackPeriod: backtest.config.scenario.loopbackPeriod
        } : undefined
      };

      return await dataClient.data_GetCandlesWithIndicators(request);
    },
    enabled: !backtest.candles || backtest.candles.length === 0, // Only run query if no candles exist
    staleTime: 5 * 60 * 1000, // 5 minutes
    gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime)
  });

  // Use the full backtest data if available, otherwise fallback to the original backtest
  const currentBacktest = fullBacktestData || backtest;
  
  // Use the data from query or fallback to backtest data
  const candles = candlesData?.candles || currentBacktest.candles || [];
  const indicatorsValues = candlesData?.indicatorsValues || {};

  // Convert positions and signals objects to arrays
  const positionsArray: Position[] = Object.values(currentBacktest.positions || {});
  const signalsArray: LightSignal[] = Object.values(currentBacktest.signals || {});
  const walletBalances = currentBacktest.walletBalances || [];
  const statistics = currentBacktest.statistics;
  const config = currentBacktest.config;

  // Helper function to calculate position open time in hours
  const calculateOpenTimeInHours = (position: Position): number => {
    const openDate = new Date(position.Open.date);
    let closeDate: Date | null = null;

    // Determine close date based on realized P&L (matching backend logic)
    if (position.ProfitAndLoss?.realized != null) {
      if (position.ProfitAndLoss.realized > 0) {
        // Profitable close = Take Profit
        closeDate = new Date(position.TakeProfit1.date);
      } else {
        // Loss or breakeven close = Stop Loss
        closeDate = new Date(position.StopLoss.date);
      }
    }

    if (closeDate) {
      const diffInMs = closeDate.getTime() - openDate.getTime();
      return diffInMs / (1000 * 60 * 60); // Convert to hours
    }
    return 0;
  };

  // Calculate average open time for winning positions
  const getAverageOpenTimeWinning = (): string => {
    const winningPositions = positionsArray.filter((p: Position) => {
      const realized = p.ProfitAndLoss?.realized ?? 0;
      return realized > 0;
    });

    if (winningPositions.length === 0) return "0.00";

    const totalHours = winningPositions.reduce((sum: number, position: Position) => {
      return sum + calculateOpenTimeInHours(position);
    }, 0);

    const averageHours = totalHours / winningPositions.length;
    return averageHours.toFixed(2);
  };

  // Calculate average open time for losing positions
  const getAverageOpenTimeLosing = (): string => {
    const losingPositions = positionsArray.filter((p: Position) => {
      const realized = p.ProfitAndLoss?.realized ?? 0;
      return realized <= 0;
    });

    if (losingPositions.length === 0) return "0.00";

    const totalHours = losingPositions.reduce((sum: number, position: Position) => {
      return sum + calculateOpenTimeInHours(position);
    }, 0);

    const averageHours = totalHours / losingPositions.length;
    return averageHours.toFixed(2);
  };

  // Calculate maximum open time for winning positions
  const getMaxOpenTimeWinning = (): string => {
    const winningPositions = positionsArray.filter((p: Position) => {
      const realized = p.ProfitAndLoss?.realized ?? 0;
      return realized > 0;
    });

    if (winningPositions.length === 0) return "0.00";

    const openTimes = winningPositions.map((position: Position) => calculateOpenTimeInHours(position));
    const maxHours = Math.max(...openTimes);
    return maxHours.toFixed(2);
  };

  // Calculate median opening time for all positions
  const getMedianOpenTime = (): string => {
    if (positionsArray.length === 0) return "0.00";

    const openTimes = positionsArray.map((position: Position) => calculateOpenTimeInHours(position));
    const sortedTimes = openTimes.sort((a: number, b: number) => a - b);
    
    const mid = Math.floor(sortedTimes.length / 2);
    const median = sortedTimes.length % 2 === 0
      ? (sortedTimes[mid - 1] + sortedTimes[mid]) / 2
      : sortedTimes[mid];
    
    return median.toFixed(2);
  };

  // Calculate total volume traded with leverage
  const getTotalVolumeTraded = (): number => {
    let totalVolume = 0;

    positionsArray.forEach((position: Position) => {
      // Calculate volume for open trade
      const openLeverage = position.Open.leverage || 1;
      const openVolume = position.Open.quantity * position.Open.price * openLeverage;
      totalVolume += openVolume;

      // Calculate volume for close trade (stopLoss or takeProfit based on realized P&L)
      if (position.ProfitAndLoss?.realized != null) {
        let closeTrade;
        if (position.ProfitAndLoss.realized > 0) {
          // Profitable close = Take Profit
          closeTrade = position.TakeProfit1;
        } else {
          // Loss or breakeven close = Stop Loss
          closeTrade = position.StopLoss;
        }

        if (closeTrade) {
          const closeLeverage = closeTrade.leverage || 1;
          const closeVolume = closeTrade.quantity * closeTrade.price * closeLeverage;
          totalVolume += closeVolume;
        }
      }
    });

    return totalVolume;
  };

  // Calculate estimated UI fee (0.02% of total volume)
  const getEstimatedUIFee = (): number => {
    const totalVolume = getTotalVolumeTraded();
    const uiFeePercentage = 0.001; // 0.1%
    return totalVolume * uiFeePercentage;
  };

  // Calculate recommended cooldown based on positions that fail after a win
  const getCooldownRecommendations = () => {
    if (positionsArray.length < 2 || !candles || candles.length < 2) {
      return { percentile75: "0", average: "0", median: "0" };
    }

    // Determine candle timeframe in milliseconds
    const candleTimeframeMs = new Date(candles[1].date).getTime() - new Date(candles[0].date).getTime();

    const sortedPositions = [...positionsArray].sort((a: Position, b: Position) => {
      const dateA = new Date(a.Open.date).getTime();
      const dateB = new Date(b.Open.date).getTime();
      return dateA - dateB;
    });

    const failAfterWinGaps: number[] = [];

    for (let i = 0; i < sortedPositions.length - 1; i++) {
      const currentPosition = sortedPositions[i];
      const nextPosition = sortedPositions[i + 1];

      const currentRealized = currentPosition.ProfitAndLoss?.realized ?? 0;
      const nextRealized = nextPosition.ProfitAndLoss?.realized ?? 0;

      // Check if current position is winning and next position is losing
      if (currentRealized > 0 && nextRealized <= 0) {
        // Calculate the close time of the current (winning) position
        let currentCloseDate: Date | null = null;
        if (currentPosition.ProfitAndLoss?.realized != null) {
          if (currentPosition.ProfitAndLoss.realized > 0) {
            currentCloseDate = new Date(currentPosition.TakeProfit1.date);
          } else {
            currentCloseDate = new Date(currentPosition.StopLoss.date);
          }
        }

        if (currentCloseDate) {
          const nextOpenDate = new Date(nextPosition.Open.date);
          const gapInMs = nextOpenDate.getTime() - currentCloseDate.getTime();
          
          if (gapInMs >= 0) { // Only consider positive gaps
            // Convert milliseconds to number of candles
            const gapInCandles = Math.floor(gapInMs / candleTimeframeMs);
            failAfterWinGaps.push(gapInCandles);
          }
        }
      }
    }

    if (failAfterWinGaps.length === 0) {
      return { percentile75: "0", average: "0", median: "0" };
    }

    // Calculate the 75th percentile
    const sortedGaps = [...failAfterWinGaps].sort((a: number, b: number) => a - b);
    const percentile75Index = Math.floor(sortedGaps.length * 0.75);
    const percentile75 = sortedGaps[percentile75Index] || 0;

    // Calculate the average
    const sum = failAfterWinGaps.reduce((acc: number, gap: number) => acc + gap, 0);
    const average = sum / failAfterWinGaps.length;

    // Calculate the median
    const mid = Math.floor(sortedGaps.length / 2);
    const median = sortedGaps.length % 2 === 0
      ? (sortedGaps[mid - 1] + sortedGaps[mid]) / 2
      : sortedGaps[mid];
    
    return {
      percentile75: Math.ceil(percentile75).toString(),
      average: Math.ceil(average).toString(),
      median: Math.ceil(median).toString()
    };
  };

  const cooldownRecommendations = getCooldownRecommendations();

  // Calculate average trades per day
  const getAverageTradesPerDay = (): string => {
    if (positionsArray.length === 0) return "0.00";

    // Get all trade dates and sort them
    const tradeDates = positionsArray.map((position: Position) => new Date(position.Open.date)).sort((a: Date, b: Date) => a.getTime() - b.getTime());
    
    if (tradeDates.length < 2) return positionsArray.length.toString();

    // Calculate the date range in days
    const firstTradeDate = tradeDates[0];
    const lastTradeDate = tradeDates[tradeDates.length - 1];
    const diffInMs = lastTradeDate.getTime() - firstTradeDate.getTime();
    const diffInDays = Math.max(1, diffInMs / (1000 * 60 * 60 * 24)); // Ensure at least 1 day

    const averageTradesPerDay = positionsArray.length / diffInDays;
    return averageTradesPerDay.toFixed(2);
  };

  return (
    <>
      <div className="grid grid-flow-row">
        {(isLoadingCandles || isLoadingFullBacktest) && (
          <div className="flex justify-center items-center p-4">
            <div className="loading loading-spinner loading-lg"></div>
            <span className="ml-2">
              {isLoadingFullBacktest ? "Loading backtest data..." : "Loading candles with indicators..."}
            </span>
          </div>
        )}
        <div className="grid grid-cols-4 p-5">
          <CardPosition
            positivePosition={true}
            positions={positionsArray.filter((p: Position) => {
              const realized = p.ProfitAndLoss?.realized ?? 0
              return realized > 0 ? p : null
            })}
          ></CardPosition>
          <CardPosition
            positivePosition={false}
            positions={positionsArray.filter((p: Position) => {
              const realized = p.ProfitAndLoss?.realized ?? 0
              return realized <= 0 ? p : null
            })}
          ></CardPosition>
          <CardPositionItem positions={positionsArray}></CardPositionItem>
          <CardText
            title="Max Drowdown"
            content={
              (statistics?.maxDrawdown?.toFixed(4) || '0.0000') +
              '$'
            }
          ></CardText>
          <CardText
            title="Sharpe Ratio"
            content={
              (statistics?.sharpeRatio
                ? statistics.sharpeRatio * 100
                : 0
              )
                .toFixed(4)
                .toString() + '%'
            }
          ></CardText>
          <CardText
            title="Max Drawdown Recovery Time"
            content={
              (statistics?.maxDrawdownRecoveryTime?.toString() || '0') +
              ' days'
            }
          ></CardText>
          <CardText
            title="Money Management"
            content={
              "SL: " +(config.moneyManagement?.stopLoss * 100).toFixed(2) + "% TP: " + 
              (config.moneyManagement?.takeProfit * 100).toFixed(2) + "%" + " Lev.: x" + config.moneyManagement?.leverage
            }
          ></CardText>
          <CardText
            title="Avg Open Time (Winning)"
            content={getAverageOpenTimeWinning() + " hours"}
          ></CardText>
          <CardText
            title="Avg Open Time (Losing)"
            content={getAverageOpenTimeLosing() + " hours"}
          ></CardText>
          <CardText
            title="Max Open Time (Winning)"
            content={getMaxOpenTimeWinning() + " hours"}
          ></CardText>
          <CardText
            title="Median Open Time"
            content={getMedianOpenTime() + " hours"}
          ></CardText>
          <CardText
            title="Volume Traded"
            content={"$" + getTotalVolumeTraded().toLocaleString('en-US', { 
              minimumFractionDigits: 2, 
              maximumFractionDigits: 2 
            })}
          ></CardText>
          <CardText
            title="Estimated UI Fee"
            content={"$" + getEstimatedUIFee().toLocaleString('en-US', { 
              minimumFractionDigits: 2, 
              maximumFractionDigits: 2 
            })}
          ></CardText>
          <CardText
            title="Total Fees"
            content={"$" + currentBacktest.fees.toLocaleString('en-US', { 
              minimumFractionDigits: 2, 
              maximumFractionDigits: 2 
            })}
          ></CardText>
          <CardText
            title="Recommended Cooldown"
            content={cooldownRecommendations.percentile75 + " candles"}
          ></CardText>
          <CardText
            title="Average Cooldown"
            content={cooldownRecommendations.average + " candles"}
          ></CardText>
          <CardText
            title="Median Cooldown"
            content={cooldownRecommendations.median + " candles"}
          ></CardText>
          <CardText
            title="Avg Trades Per Day"
            content={getAverageTradesPerDay() + " trades/day"}
          ></CardText>
        </div>
        {!isLoadingCandles && !isLoadingFullBacktest && (
          <div className="w-full">
            <figure className="w-full">
              <TradeChart
                candles={candles}
                positions={positionsArray}
                walletBalances={walletBalances}
                indicatorsValues={indicatorsValues}
                signals={signalsArray}
                height={1000}
              ></TradeChart>
            </figure>
          </div>
        )}
      </div>
    </>
  )
}

export default BacktestRowDetails
