// GRU Index Service
// Index system for LiXAU, LiPMG, LiBMG1-3

import prisma from '@/shared/database/prisma';
import { Decimal } from '@prisma/client/runtime/library';
import { v4 as uuidv4 } from 'uuid';
import { logger } from '@/infrastructure/monitoring/logger';


export type GruIndexCode = 'LiXAU' | 'LiPMG' | 'LiBMG1' | 'LiBMG2' | 'LiBMG3';

export interface GruIndexUpdate {
  indexCode: GruIndexCode;
  currentValue: string;
  volume?: string;
  metadata?: Record<string, unknown>;
}

export interface GruIndexResult {
  indexId: string;
  indexCode: string;
  indexName: string;
  baseValue: Decimal;
  currentValue: Decimal;
  changePercent?: Decimal;
  lastUpdated: Date;
  status: string;
}

export class GruIndexService {
  /**
   * Initialize GRU indexes if they don't exist
   */
  async initializeIndexes(): Promise<void> {
    const indexes = [
      {
        indexCode: 'LiXAU',
        indexName: 'Gold/XAU Index',
        description: 'Gold/XAU-based index for GRU valuation',
        calculationMethod: 'xau_based',
        baseValue: new Decimal(100), // Base value
        currentValue: new Decimal(100),
      },
      {
        indexCode: 'LiPMG',
        indexName: 'Platinum Group Metals Index',
        description: 'Platinum Group Metals index (Platinum, Palladium, Rhodium)',
        calculationMethod: 'pgm_based',
        baseValue: new Decimal(40),
        currentValue: new Decimal(40),
        weightings: {
          platinum: 0.5,
          palladium: 0.3,
          rhodium: 0.2,
        },
      },
      {
        indexCode: 'LiBMG1',
        indexName: 'Base Metal Group 1 Index',
        description: 'Primary base metals index (Copper, Nickel, Zinc)',
        calculationMethod: 'bmg_weighted',
        baseValue: new Decimal(30),
        currentValue: new Decimal(30),
        weightings: {
          copper: 0.5,
          nickel: 0.3,
          zinc: 0.2,
        },
      },
      {
        indexCode: 'LiBMG2',
        indexName: 'Base Metal Group 2 Index',
        description: 'Secondary base metals index (Aluminum, Lead, Tin)',
        calculationMethod: 'bmg_weighted',
        baseValue: new Decimal(20),
        currentValue: new Decimal(20),
        weightings: {
          aluminum: 0.5,
          lead: 0.3,
          tin: 0.2,
        },
      },
      {
        indexCode: 'LiBMG3',
        indexName: 'Base Metal Group 3 Index',
        description: 'Tertiary base metals index (Iron Ore, Manganese, Chromium)',
        calculationMethod: 'bmg_weighted',
        baseValue: new Decimal(10),
        currentValue: new Decimal(10),
        weightings: {
          ironOre: 0.5,
          manganese: 0.3,
          chromium: 0.2,
        },
      },
    ];

    for (const indexData of indexes) {
      const existing = await prisma.gru_indexes.findUnique({
        where: { indexCode: indexData.indexCode },
      });

      if (!existing) {
        await prisma.gru_indexes.create({
          data: {
            indexId: `GRU-IDX-${uuidv4()}`,
            indexCode: indexData.indexCode,
            indexName: indexData.indexName,
            description: indexData.description,
            calculationMethod: indexData.calculationMethod,
            baseValue: indexData.baseValue,
            currentValue: indexData.currentValue,
            weightings: indexData.weightings || null,
            updateFrequency: 'real_time',
            status: 'active',
          },
        });

        logger.info(`GRU Index initialized: ${indexData.indexCode}`);
      }
    }
  }

  /**
   * Get index by code
   */
  async getIndex(indexCode: GruIndexCode): Promise<GruIndexResult | null> {
    const index = await prisma.gru_indexes.findUnique({
      where: { indexCode },
    });

    if (!index) {
      return null;
    }

    // Calculate change percent from base
    const changePercent = index.currentValue
      .minus(index.baseValue)
      .dividedBy(index.baseValue)
      .times(100);

    return {
      indexId: index.indexId,
      indexCode: index.indexCode,
      indexName: index.indexName,
      baseValue: index.baseValue,
      currentValue: index.currentValue,
      changePercent,
      lastUpdated: index.lastUpdated,
      status: index.status,
    };
  }

  /**
   * Get all indexes
   */
  async getAllIndexes(): Promise<GruIndexResult[]> {
    const indexes = await prisma.gru_indexes.findMany({
      where: { status: 'active' },
      orderBy: { indexCode: 'asc' },
    });

    return indexes.map((index) => {
      const changePercent = index.currentValue
        .minus(index.baseValue)
        .dividedBy(index.baseValue)
        .times(100);

      return {
        indexId: index.indexId,
        indexCode: index.indexCode,
        indexName: index.indexName,
        baseValue: index.baseValue,
        currentValue: index.currentValue,
        changePercent,
        lastUpdated: index.lastUpdated,
        status: index.status,
      };
    });
  }

  /**
   * Update index value
   */
  async updateIndex(update: GruIndexUpdate): Promise<GruIndexResult> {
    const index = await prisma.gru_indexes.findUnique({
      where: { indexCode: update.indexCode },
    });

    if (!index) {
      throw new Error(`Index not found: ${update.indexCode}`);
    }

    const newValue = new Decimal(update.currentValue);
    const previousValue = index.currentValue;

    // Calculate change percent
    const changePercent = newValue
      .minus(previousValue)
      .dividedBy(previousValue)
      .times(100);

    // Update index
    const updated = await prisma.gru_indexes.update({
      where: { indexCode: update.indexCode },
      data: {
        currentValue: newValue,
        lastUpdated: new Date(),
      },
    });

    // Save price history
    await prisma.gru_index_price_history.create({
      data: {
        historyId: `GRU-HIST-${uuidv4()}`,
        indexId: index.indexId,
        indexCode: update.indexCode,
        indexValue: newValue,
        changePercent,
        volume: update.volume ? new Decimal(update.volume) : null,
        metadata: update.metadata || null,
      },
    });

    logger.info(`GRU Index updated: ${update.indexCode} = ${newValue.toString()}`);

    return {
      indexId: updated.indexId,
      indexCode: updated.indexCode,
      indexName: updated.indexName,
      baseValue: updated.baseValue,
      currentValue: updated.currentValue,
      changePercent,
      lastUpdated: updated.lastUpdated,
      status: updated.status,
    };
  }

  /**
   * Calculate index value from underlying assets
   * For composite indexes (LiPMG, LiBMG1-3)
   */
  async calculateIndexValue(
    indexCode: GruIndexCode,
    assetPrices: Record<string, string>
  ): Promise<Decimal> {
    const index = await prisma.gru_indexes.findUnique({
      where: { indexCode },
    });

    if (!index) {
      throw new Error(`Index not found: ${indexCode}`);
    }

    if (indexCode === 'LiXAU') {
      // LiXAU is directly XAU-based
      const xauPrice = assetPrices['XAU'] || assetPrices['GOLD'];
      if (!xauPrice) {
        throw new Error('XAU price required for LiXAU calculation');
      }
      return new Decimal(xauPrice);
    }

    // For composite indexes, use weighted calculation
    if (!index.weightings) {
      throw new Error(`Index ${indexCode} has no weightings defined`);
    }

    const weightings = index.weightings as Record<string, number>;
    let weightedSum = new Decimal(0);

    for (const [asset, weight] of Object.entries(weightings)) {
      const price = assetPrices[asset];
      if (!price) {
        logger.warn(`Price not found for asset: ${asset} in index ${indexCode}`);
        continue;
      }

      const assetValue = new Decimal(price);
      weightedSum = weightedSum.plus(assetValue.times(weight));
    }

    return weightedSum;
  }

  /**
   * Get index price history
   */
  async getIndexHistory(
    indexCode: GruIndexCode,
    limit: number = 100,
    startDate?: Date,
    endDate?: Date
  ) {
    const where: any = { indexCode };

    if (startDate || endDate) {
      where.timestamp = {};
      if (startDate) where.timestamp.gte = startDate;
      if (endDate) where.timestamp.lte = endDate;
    }

    return await prisma.gru_index_price_history.findMany({
      where,
      orderBy: { timestamp: 'desc' },
      take: limit,
    });
  }

  /**
   * Get GRU valuation matrix
   * Returns matrix of GRU tiers vs indexes
   */
  async getValuationMatrix(): Promise<Record<string, Record<string, Decimal>>> {
    const indexes = await this.getAllIndexes();
    const matrix: Record<string, Record<string, Decimal>> = {
      M00: {},
      M0: {},
      M1: {},
    };

    for (const index of indexes) {
      // M00 GRU valuations
      switch (index.indexCode) {
        case 'LiXAU':
          matrix.M00.LiXAU = new Decimal(100); // 1 M00 = 100 LiXAU
          break;
        case 'LiPMG':
          matrix.M00.LiPMG = new Decimal(40);
          break;
        case 'LiBMG1':
          matrix.M00.LiBMG1 = new Decimal(30);
          break;
        case 'LiBMG2':
          matrix.M00.LiBMG2 = new Decimal(20);
          break;
        case 'LiBMG3':
          matrix.M00.LiBMG3 = new Decimal(10);
          break;
      }
    }

    // M0 = M00 / 5
    for (const [indexCode, value] of Object.entries(matrix.M00)) {
      matrix.M0[indexCode] = value.dividedBy(5);
    }

    // M1 = M00 / 25
    for (const [indexCode, value] of Object.entries(matrix.M00)) {
      matrix.M1[indexCode] = value.dividedBy(25);
    }

    return matrix;
  }
}

export const gruIndexService = new GruIndexService();

