import axios from 'axios';
import { IServiceContainer } from '@stock-bot/di';
import { PerformanceMetrics, RiskMetrics } from '../types';
import { StorageService } from './StorageService';

interface OptimizationParams {
  returns: number[][];
  constraints?: {
    minWeight?: number;
    maxWeight?: number;
    targetReturn?: number;
    maxRisk?: number;
  };
}

interface PortfolioWeights {
  symbols: string[];
  weights: number[];
  expectedReturn: number;
  expectedRisk: number;
  sharpeRatio: number;
}

export class AnalyticsService {
  private analyticsUrl: string;
  private cache = new Map<string, { data: any; timestamp: number }>();
  private readonly CACHE_TTL_MS = 60000; // 1 minute cache
  private container: IServiceContainer;
  private storageService: StorageService;

  constructor(container: IServiceContainer, storageService: StorageService) {
    this.container = container;
    this.storageService = storageService;
    this.analyticsUrl = process.env.ANALYTICS_SERVICE_URL || 'http://localhost:3003';
  }

  async getPerformanceMetrics(
    portfolioId: string,
    startDate: Date,
    endDate: Date
  ): Promise<PerformanceMetrics> {
    const cacheKey = `perf_${portfolioId}_${startDate.toISOString()}_${endDate.toISOString()}`;
    const cached = this.getFromCache(cacheKey);
    if (cached) return cached;

    try {
      const response = await axios.get(`${this.analyticsUrl}/analytics/performance/${portfolioId}`, {
        params: {
          start_date: startDate.toISOString(),
          end_date: endDate.toISOString()
        }
      });

      const metrics = response.data as PerformanceMetrics;
      this.setCache(cacheKey, metrics);
      return metrics;
    } catch (error) {
      this.container.logger.error('Error fetching performance metrics:', error);
      // Return default metrics if analytics service is unavailable
      return this.getDefaultPerformanceMetrics();
    }
  }

  async optimizePortfolio(params: OptimizationParams): Promise<PortfolioWeights> {
    try {
      const response = await axios.post(`${this.analyticsUrl}/optimize/portfolio`, params);
      return response.data as PortfolioWeights;
    } catch (error) {
      this.container.logger.error('Error optimizing portfolio:', error);
      // Return equal weights as fallback
      return this.getEqualWeights(params.returns[0].length);
    }
  }

  async getRiskMetrics(portfolioId: string): Promise<RiskMetrics> {
    const cacheKey = `risk_${portfolioId}`;
    const cached = this.getFromCache(cacheKey);
    if (cached) return cached;

    try {
      const response = await axios.get(`${this.analyticsUrl}/analytics/risk/${portfolioId}`);
      const metrics = response.data as RiskMetrics;
      this.setCache(cacheKey, metrics);
      return metrics;
    } catch (error) {
      this.container.logger.error('Error fetching risk metrics:', error);
      return this.getDefaultRiskMetrics();
    }
  }

  async detectMarketRegime(): Promise<string> {
    const cacheKey = 'market_regime';
    const cached = this.getFromCache(cacheKey);
    if (cached) return cached;

    try {
      const response = await axios.get(`${this.analyticsUrl}/analytics/regime`);
      const regime = response.data.regime as string;
      this.setCache(cacheKey, regime, 300000); // Cache for 5 minutes
      return regime;
    } catch (error) {
      this.container.logger.error('Error detecting market regime:', error);
      return 'normal'; // Default regime
    }
  }

  async calculateCorrelationMatrix(symbols: string[]): Promise<number[][]> {
    try {
      const response = await axios.post(`${this.analyticsUrl}/analytics/correlation`, { symbols });
      return response.data.matrix as number[][];
    } catch (error) {
      this.container.logger.error('Error calculating correlation matrix:', error);
      // Return identity matrix as fallback
      return this.getIdentityMatrix(symbols.length);
    }
  }

  async runBacktestAnalysis(backtestId: string): Promise<any> {
    try {
      const response = await axios.get(`${this.analyticsUrl}/analytics/backtest/${backtestId}`);
      return response.data;
    } catch (error) {
      this.container.logger.error('Error running backtest analysis:', error);
      return null;
    }
  }

  async predictWithModel(modelId: string, features: Record<string, number>): Promise<any> {
    try {
      const response = await axios.post(`${this.analyticsUrl}/models/predict`, {
        model_id: modelId,
        features
      });
      return response.data;
    } catch (error) {
      this.container.logger.error('Error getting model prediction:', error);
      return null;
    }
  }

  // Cache management
  private getFromCache(key: string): any | null {
    const cached = this.cache.get(key);
    if (!cached) return null;
    
    const now = Date.now();
    if (now - cached.timestamp > this.CACHE_TTL_MS) {
      this.cache.delete(key);
      return null;
    }
    
    return cached.data;
  }

  private setCache(key: string, data: any, ttl?: number): void {
    this.cache.set(key, {
      data,
      timestamp: Date.now()
    });
    
    // Auto-cleanup after TTL
    setTimeout(() => {
      this.cache.delete(key);
    }, ttl || this.CACHE_TTL_MS);
  }

  // Fallback methods when analytics service is unavailable
  private getDefaultPerformanceMetrics(): PerformanceMetrics {
    return {
      totalReturn: 0,
      sharpeRatio: 0,
      sortinoRatio: 0,
      maxDrawdown: 0,
      winRate: 0,
      profitFactor: 0,
      avgWin: 0,
      avgLoss: 0,
      totalTrades: 0
    };
  }

  private getDefaultRiskMetrics(): RiskMetrics {
    return {
      currentExposure: 0,
      dailyPnl: 0,
      positionCount: 0,
      grossExposure: 0,
      var95: 0,
      cvar95: 0
    };
  }

  private getEqualWeights(n: number): PortfolioWeights {
    const weight = 1 / n;
    return {
      symbols: Array(n).fill('').map((_, i) => `Asset${i + 1}`),
      weights: Array(n).fill(weight),
      expectedReturn: 0,
      expectedRisk: 0,
      sharpeRatio: 0
    };
  }

  private getIdentityMatrix(n: number): number[][] {
    const matrix: number[][] = [];
    for (let i = 0; i < n; i++) {
      matrix[i] = [];
      for (let j = 0; j < n; j++) {
        matrix[i][j] = i === j ? 1 : 0;
      }
    }
    return matrix;
  }
}