/**
 * useMonteCarloSimulation Hook
 * Handles Monte Carlo simulation API calls and state management
 */

import { useState, useCallback } from 'react';
import { useQuery, useMutation } from '@tanstack/react-query';

// ============================================================================
// Types
// ============================================================================

export interface MonteCarloParams {
  numSimulations: number;
  timeHorizon: number;
  targetReturn: number;
  annualReturn?: number;
  volatility?: number;
}

export interface SimulationResult {
  paths: number[][];
  finalValues: number[];
  percentiles: {
    p5: number;
    p10: number;
    p25: number;
    p50: number;
    p75: number;
    p90: number;
    p95: number;
  };
  var95: number;
  var99: number;
  cvar95: number;
  expectedValue: number;
  medianValue: number;
  probabilityOfLoss: number;
  probabilityOfTarget: number;
  simulationDate: string;
}

export interface UseMonteCarloSimulationOptions {
  onSuccess?: (result: SimulationResult) => void;
  onError?: (error: Error) => void;
}

// ============================================================================
// API Functions
// ============================================================================

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3080';

async function runMonteCarloSimulation(
  portfolioId: string,
  params: MonteCarloParams
): Promise<SimulationResult> {
  const response = await fetch(
    `${API_URL}/api/v1/portfolio/${portfolioId}/monte-carlo`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'include',
      body: JSON.stringify(params),
    }
  );

  if (!response.ok) {
    throw new Error('Failed to run Monte Carlo simulation');
  }

  const data = await response.json();
  return data.data || data;
}

async function getLastSimulation(portfolioId: string): Promise<SimulationResult | null> {
  const response = await fetch(
    `${API_URL}/api/v1/portfolio/${portfolioId}/monte-carlo/latest`,
    { credentials: 'include' }
  );

  if (!response.ok) {
    if (response.status === 404) return null;
    throw new Error('Failed to fetch last simulation');
  }

  const data = await response.json();
  return data.data || data;
}

// ============================================================================
// Hook
// ============================================================================

export function useMonteCarloSimulation(
  portfolioId: string,
  options: UseMonteCarloSimulationOptions = {}
) {
  const [localResult, setLocalResult] = useState<SimulationResult | null>(null);

  // Fetch last simulation result
  const lastSimulationQuery = useQuery({
    queryKey: ['monte-carlo', portfolioId, 'latest'],
    queryFn: () => getLastSimulation(portfolioId),
    enabled: !!portfolioId,
    staleTime: 5 * 60 * 1000, // 5 minutes
  });

  // Run simulation mutation
  const simulationMutation = useMutation({
    mutationFn: (params: MonteCarloParams) =>
      runMonteCarloSimulation(portfolioId, params),
    onSuccess: (result) => {
      setLocalResult(result);
      options.onSuccess?.(result);
    },
    onError: (error: Error) => {
      options.onError?.(error);
    },
  });

  // Run simulation locally (for faster response without API)
  const runLocalSimulation = useCallback(
    (
      currentValue: number,
      params: MonteCarloParams
    ): SimulationResult => {
      const {
        numSimulations,
        timeHorizon,
        targetReturn,
        annualReturn = 0.12,
        volatility = 0.25,
      } = params;

      // Daily parameters
      const tradingDays = timeHorizon;
      const dailyReturn = annualReturn / 252;
      const dailyVolatility = volatility / Math.sqrt(252);

      const paths: number[][] = [];
      const finalValues: number[] = [];

      // Run simulations
      for (let sim = 0; sim < numSimulations; sim++) {
        const path: number[] = [currentValue];
        let value = currentValue;

        for (let day = 1; day <= tradingDays; day++) {
          // Box-Muller transform for normal distribution
          const u1 = Math.random();
          const u2 = Math.random();
          const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);

          // Geometric Brownian Motion
          const drift = dailyReturn - 0.5 * dailyVolatility * dailyVolatility;
          const diffusion = dailyVolatility * z;
          value = value * Math.exp(drift + diffusion);

          // Store path points for visualization (first 100 paths only)
          if (sim < 100 && day % Math.max(1, Math.floor(tradingDays / 50)) === 0) {
            path.push(value);
          }
        }

        if (sim < 100) {
          paths.push(path);
        }
        finalValues.push(value);
      }

      // Sort for percentile calculations
      finalValues.sort((a, b) => a - b);

      const getPercentile = (p: number): number => {
        const index = Math.floor((p / 100) * finalValues.length);
        return finalValues[Math.min(index, finalValues.length - 1)];
      };

      const percentiles = {
        p5: getPercentile(5),
        p10: getPercentile(10),
        p25: getPercentile(25),
        p50: getPercentile(50),
        p75: getPercentile(75),
        p90: getPercentile(90),
        p95: getPercentile(95),
      };

      // VaR calculations
      const var95 = currentValue - getPercentile(5);
      const var99 = currentValue - getPercentile(1);

      // CVaR (Expected Shortfall)
      const var95Index = Math.floor(0.05 * finalValues.length);
      const worstValues = finalValues.slice(0, var95Index);
      const cvar95 =
        currentValue -
        (worstValues.reduce((a, b) => a + b, 0) / worstValues.length);

      // Statistics
      const expectedValue =
        finalValues.reduce((a, b) => a + b, 0) / finalValues.length;
      const probabilityOfLoss =
        (finalValues.filter((v) => v < currentValue).length / finalValues.length) *
        100;
      const targetValue = currentValue * (1 + targetReturn / 100);
      const probabilityOfTarget =
        (finalValues.filter((v) => v >= targetValue).length / finalValues.length) *
        100;

      const result: SimulationResult = {
        paths,
        finalValues,
        percentiles,
        var95,
        var99,
        cvar95,
        expectedValue,
        medianValue: percentiles.p50,
        probabilityOfLoss,
        probabilityOfTarget,
        simulationDate: new Date().toISOString(),
      };

      setLocalResult(result);
      return result;
    },
    []
  );

  return {
    // Data
    result: localResult || lastSimulationQuery.data,
    lastSimulation: lastSimulationQuery.data,

    // Loading states
    isLoading: lastSimulationQuery.isLoading,
    isSimulating: simulationMutation.isPending,

    // Error states
    error: lastSimulationQuery.error || simulationMutation.error,

    // Actions
    runSimulation: simulationMutation.mutate,
    runSimulationAsync: simulationMutation.mutateAsync,
    runLocalSimulation,
    clearResult: () => setLocalResult(null),

    // Refetch
    refetch: lastSimulationQuery.refetch,
  };
}

export default useMonteCarloSimulation;
