import { ClassError, ObjectBase } from '@tomei/general';
import { ApplicationLog } from '@tomei/log';
import {
  ITomeiPriceHistoryFindAllAttr,
  ITomeiPriceHistoryAttr,
} from '../../interfaces/price-tomei-price-history-attr.interface';
import { TomeiPriceHistoryRepository } from './tomei-price.repository';
import { TomeiPriceLatestRepository } from './tomei-price-latest.repository';
import { LoginUser } from '@tomei/sso';
import { Op, Sequelize } from 'sequelize';
import FeedHistoryModel from '../../models/price-feed-history.entity';
import { IFeedHistoryAttr } from '../../interfaces';
import { FeedManualPriceHistory } from '../feed-manual-price-history/feed-manual-price-history';
import { Feed } from '../feed/feed';
import {
  ManualPriceStatus,
  NonSourceFeedName,
  PricingLogic,
} from '../../enum/feed.enum';
import { CompanyFeedAccess } from '../company-feed-access/company-feed-access';

export class TomeiPriceHistory
  extends ObjectBase
  implements ITomeiPriceHistoryAttr
{
  ObjectId: string;
  ObjectName: string;
  ObjectType: string;
  TableName: string;
  FeedHistoryId: string;
  FeedName: NonSourceFeedName;
  DateTime: Date;
  AdjustBuyPercentage: number;
  AdjustSellPercentage: number;
  TomeiBuyPrice: number;
  TomeiSellPrice: number;
  AdjustedById: string;
  Currency: string;
  AdjustedDateTime: Date;
  IsManualPriceActivatedYN: string;
  BuyLabourCharges: number;
  SellLabourCharges: number;
  CompanyCode: string; // Added for multi-company support

  private static logger = new ApplicationLog('live-price', 'TomeiPriceHistory');

  get TomeiPriceHistoryId(): string {
    return this.ObjectId;
  }

  set TomeiPriceHistoryId(value: string) {
    this.ObjectId = value;
  }

  private static _Repo = new TomeiPriceHistoryRepository();
  private static _LatestRepo = new TomeiPriceLatestRepository();

  private constructor(tomeiPriceHistoryAttr?: ITomeiPriceHistoryAttr) {
    super();
    if (tomeiPriceHistoryAttr) {
      this.TomeiPriceHistoryId = tomeiPriceHistoryAttr.TomeiPriceHistoryId;
      this.FeedHistoryId = tomeiPriceHistoryAttr.FeedHistoryId;
      this.FeedName = tomeiPriceHistoryAttr.FeedName;
      this.DateTime = tomeiPriceHistoryAttr.DateTime;
      this.AdjustBuyPercentage = tomeiPriceHistoryAttr.AdjustBuyPercentage;
      this.AdjustSellPercentage = tomeiPriceHistoryAttr.AdjustSellPercentage;
      this.TomeiBuyPrice = tomeiPriceHistoryAttr.TomeiBuyPrice;
      this.TomeiSellPrice = tomeiPriceHistoryAttr.TomeiSellPrice;
      this.AdjustedById = tomeiPriceHistoryAttr.AdjustedById;
      this.Currency = tomeiPriceHistoryAttr.Currency;
      this.AdjustedDateTime = tomeiPriceHistoryAttr.AdjustedDateTime;
      this.IsManualPriceActivatedYN =
        tomeiPriceHistoryAttr.IsManualPriceActivatedYN;
      this.BuyLabourCharges = tomeiPriceHistoryAttr.BuyLabourCharges;
      this.SellLabourCharges = tomeiPriceHistoryAttr.SellLabourCharges;
      this.CompanyCode = tomeiPriceHistoryAttr.CompanyCode;
    }
  }

  public static async init(
    dbTransaction?: any,
    tomeiPriceHistoryId?: string,
  ): Promise<TomeiPriceHistory> {
    try {
      if (tomeiPriceHistoryId) {
        const data = await TomeiPriceHistory._Repo.findByPk(
          tomeiPriceHistoryId,
          dbTransaction,
        );
        if (data) {
          return new TomeiPriceHistory(data);
        } else {
          const error = new ClassError(
            'TomeiPriceHistory',
            'TomeiPriceHistoryErrMsg00',
            'TomeiPriceHistory Not Found',
          );
          await TomeiPriceHistory.logger.error({
            error,
            methodName: 'init',
            transaction: dbTransaction,
          });
          throw error;
        }
      }
      return new TomeiPriceHistory();
    } catch (error) {
      await TomeiPriceHistory.logger.error({
        error,
        methodName: 'init',
        transaction: dbTransaction,
      });
      throw new ClassError(
        'TomeiPriceHistory',
        'TomeiPriceHistoryErrMsg00',
        'TomeiPriceHistory To Initialize FeedHistory',
      );
    }
  }

  public async saveTomeiPrice(
    feedName: NonSourceFeedName,
    feedHistory: IFeedHistoryAttr,
    loginUser?: LoginUser,
    adjustmentPercentage?: {
      adjustBuyPercentage: number;
      adjustSellPercentage: number;
      buyLabourCharges: number;
      sellLabourCharges: number;
    },
    manualPrice?: {
      buyPrice: number;
      sellPrice: number;
    },
    companyCode: string = 'CTH',
    dbTransaction?: any,
  ): Promise<TomeiPriceHistory> {
    try {
      //Validate that feedHistoryId is not null
      if (!feedHistory || !feedHistory.FeedHistoryId) {
        const error = new ClassError(
          'TomeiPriceHistory',
          'TomeiPriceHistoryErrMsg01',
          'FeedHistoryId is required to create tomei price',
        );
        await TomeiPriceHistory.logger.error({
          error,
          methodName: 'saveTomeiPrice',
          transaction: dbTransaction,
        });
        throw error;
      }

      //Check if adjustmentPercentage is provided, if not retrieve the previous tomei price record for feedHistory.FeedName and feedHistory.Currency
      let adjustmentBy: string = null;
      let adjustmentDateTime: Date = new Date();
      const latestPrice = await TomeiPriceHistory.getLatestPrice(
        feedName,
        feedHistory.Currency,
        companyCode, // Pass companyCode for proper filtering
        undefined,
        dbTransaction,
      );
      if (!adjustmentPercentage) {
        //If no previous tomei price record is found, set the adjustmentPercentage to 0
        if (!latestPrice) {
          adjustmentPercentage = {
            adjustBuyPercentage: 0,
            adjustSellPercentage: 0,
            buyLabourCharges: 0,
            sellLabourCharges: 0,
          };
        } else {
          adjustmentPercentage = {
            adjustBuyPercentage: latestPrice.AdjustBuyPercentage,
            adjustSellPercentage: latestPrice.AdjustSellPercentage,
            buyLabourCharges: latestPrice.BuyLabourCharges,
            sellLabourCharges: latestPrice.SellLabourCharges,
          };
          adjustmentBy = latestPrice.AdjustedById;
          adjustmentDateTime = latestPrice.AdjustedDateTime;
        }
      } else if (!loginUser && adjustmentPercentage) {
        //If adjustmentPercentage is provided, validate that login user is provided if adjustmentPercentage is provided
        const error = new ClassError(
          'TomeiPriceHistory',
          'TomeiPriceHistoryErrMsg01',
          'Login user is required to create tomei price when updating adjustment buy and sell percentage',
        );
        await TomeiPriceHistory.logger.error({
          error,
          methodName: 'saveTomeiPrice',
          transaction: dbTransaction,
        });
        throw error;
      }

      // Calculate Tomei prices based on priority: manual price > feed manual price > calculated price
      const priceResult = await this.calculateTomeiPrices(
        feedName,
        feedHistory,
        adjustmentPercentage,
        companyCode,
        manualPrice,
        latestPrice,
        dbTransaction,
      );

      const { tomeiBuyPrice, tomeiSellPrice, IsManualPriceActivatedYN } = priceResult;

      const payload: ITomeiPriceHistoryAttr = {
        TomeiPriceHistoryId: this.createId(),
        FeedHistoryId: feedHistory.FeedHistoryId,
        FeedName: feedName,
        DateTime: new Date(),
        Currency: feedHistory.Currency,
        AdjustBuyPercentage: adjustmentPercentage.adjustBuyPercentage,
        AdjustSellPercentage: adjustmentPercentage.adjustSellPercentage,
        TomeiBuyPrice: tomeiBuyPrice,
        TomeiSellPrice: tomeiSellPrice,
        AdjustedById: loginUser ? loginUser.ObjectId : adjustmentBy,
        AdjustedDateTime: adjustmentDateTime,
        IsManualPriceActivatedYN: IsManualPriceActivatedYN,
        BuyLabourCharges: adjustmentPercentage.buyLabourCharges,
        SellLabourCharges: adjustmentPercentage.sellLabourCharges,
        CompanyCode: companyCode,
      };

      const tomeiPriceHistory = await TomeiPriceHistory._Repo.create(payload, {
        transaction: dbTransaction,
      });

      if (tomeiPriceHistory) {
        await TomeiPriceHistory.upsertTomeiPriceLatest(payload, dbTransaction);
        return new TomeiPriceHistory(tomeiPriceHistory);
      } else {
        const error = new ClassError(
          'TomeiPriceHistory',
          'TomeiPriceHistoryErrMsg01',
          'TomeiPriceHistory To Save TomeiPrice',
        );
        throw error;
      }
    } catch (error) {
      await TomeiPriceHistory.logger.error({
        error,
        methodName: 'saveTomeiPrice',
        transaction: dbTransaction,
      });
      throw error;
    }
  }

  private static async upsertTomeiPriceLatest(
    payload: ITomeiPriceHistoryAttr,
    dbTransaction?: any,
  ): Promise<void> {
    try {
      const latestPayload = {
        FeedName: payload.FeedName,
        TomeiPriceHistoryId: payload.TomeiPriceHistoryId,
        Currency: payload.Currency,
        CompanyCode: payload.CompanyCode,
        DateTime: payload.DateTime,
        AdjustBuyPercentage: payload.AdjustBuyPercentage,
        AdjustSellPercentage: payload.AdjustSellPercentage,
        TomeiBuyPrice: payload.TomeiBuyPrice,
        TomeiSellPrice: payload.TomeiSellPrice,
        AdjustedById: payload.AdjustedById,
        AdjustedDateTime: payload.AdjustedDateTime,
        UpdatedAt: new Date(),
      };

      const existingRecord = await TomeiPriceHistory._LatestRepo.findOne({
        where: {
          FeedName: payload.FeedName,
          Currency: payload.Currency,
        },
        transaction: dbTransaction,
      });

      if (existingRecord) {
        await TomeiPriceHistory._LatestRepo.update(latestPayload, {
          where: {
            FeedName: payload.FeedName,
            Currency: payload.Currency,
          },
          transaction: dbTransaction,
        });
      } else {
        await TomeiPriceHistory._LatestRepo.create(latestPayload, {
          transaction: dbTransaction,
        });
      }
    } catch (error) {
      await TomeiPriceHistory.logger.error({
        error,
        methodName: 'upsertTomeiPriceLatest',
        transaction: dbTransaction,
      });
      throw error;
    }
  }

  
  // Calculate Tomei prices based on different pricing strategies
  // Priority: Manual price > Feed manual price > Calculated price
  private async calculateTomeiPrices(
    feedName: NonSourceFeedName,
    feedHistory: IFeedHistoryAttr,
    adjustmentPercentage: {
      adjustBuyPercentage: number;
      adjustSellPercentage: number;
      buyLabourCharges: number;
      sellLabourCharges: number;
    },
    companyCode: string,
    manualPrice?: { buyPrice: number; sellPrice: number },
    latestPrice?: ITomeiPriceHistoryAttr,
    dbTransaction?: any,
  ): Promise<{
    tomeiBuyPrice: number;
    tomeiSellPrice: number;
    IsManualPriceActivatedYN: string;
  }> {
    // 1. Check for direct manual price override
    if (manualPrice) {
      return this.applyManualPrice(manualPrice);
    }

    // 2. Check for active feed manual price
    const feedManualPrice = await this.getFeedManualPrice(
      feedName,
      companyCode,
      dbTransaction,
    );
    
    if (feedManualPrice) {
      return this.applyFeedManualPrice(feedManualPrice, latestPrice);
    }

    // 3. Calculate price using feed history and adjustments
    return await this.calculatePriceFromFeed(
      feedName,
      feedHistory,
      adjustmentPercentage,
      companyCode,
      dbTransaction,
    );
  }

 
  // Apply direct manual price override
  private applyManualPrice(manualPrice: { buyPrice: number; sellPrice: number }): {
    tomeiBuyPrice: number;
    tomeiSellPrice: number;
    IsManualPriceActivatedYN: string;
  } {
    return {
      tomeiBuyPrice: manualPrice.buyPrice,
      tomeiSellPrice: manualPrice.sellPrice,
      IsManualPriceActivatedYN: 'Y',
    };
  }

  
  //Get active feed manual price if exists
  private async getFeedManualPrice(
    feedName: NonSourceFeedName,
    companyCode: string,
    dbTransaction?: any,
  ) {
    const feedManualPrice = await FeedManualPriceHistory.findAll(
      1,
      1,
      {
        FeedName: feedName,
        Status: ManualPriceStatus.ACTIVE,
        ...(companyCode && { CompanyCode: companyCode }),
      },
      dbTransaction,
    );

    return feedManualPrice.count > 0 ? feedManualPrice.rows[0] : null;
  }

  // Apply feed manual price with fallback to latest price
  private applyFeedManualPrice(
    feedManualPrice: any,
    latestPrice?: ITomeiPriceHistoryAttr,
  ): {
    tomeiBuyPrice: number;
    tomeiSellPrice: number;
    IsManualPriceActivatedYN: string;
  } {
    return {
      tomeiBuyPrice: feedManualPrice.BuyPrice ?? latestPrice?.TomeiBuyPrice ?? 0,
      tomeiSellPrice: feedManualPrice.SellPrice ?? latestPrice?.TomeiSellPrice ?? 0,
      IsManualPriceActivatedYN: 'Y',
    };
  }

  // Calculate price from feed history using pricing logic and adjustments
  private async calculatePriceFromFeed(
    feedName: NonSourceFeedName,
    feedHistory: IFeedHistoryAttr,
    adjustmentPercentage: {
      adjustBuyPercentage: number;
      adjustSellPercentage: number;
      buyLabourCharges: number;
      sellLabourCharges: number;
    },
    companyCode: string,
    dbTransaction?: any,
  ): Promise<{
    tomeiBuyPrice: number;
    tomeiSellPrice: number;
    IsManualPriceActivatedYN: string;
  }> {
    // Handle invalid feed prices
    if (feedHistory.FeedBuyPrice <= 0 || feedHistory.FeedSellPrice <= 0) {
      return {
        tomeiBuyPrice: 0,
        tomeiSellPrice: 0,
        IsManualPriceActivatedYN: 'N',
      };
    }

    const feed = await Feed.init(dbTransaction, feedName);
    const weight = feed.Weight ?? 0;

    const pricingLogic = await CompanyFeedAccess.getPricingLogic(
      dbTransaction,
      feedName,
      companyCode,
    );

    return this.applyPricingLogic(
      feedHistory,
      weight,
      adjustmentPercentage,
      pricingLogic,
    );
  }

  // Apply pricing logic calculations
  private applyPricingLogic(
    feedHistory: IFeedHistoryAttr,
    weight: number,
    adjustmentPercentage: {
      adjustBuyPercentage: number;
      adjustSellPercentage: number;
      buyLabourCharges: number;
      sellLabourCharges: number;
    },
    pricingLogic: PricingLogic,
  ): {
    tomeiBuyPrice: number;
    tomeiSellPrice: number;
    IsManualPriceActivatedYN: string;
  } {
    if (pricingLogic === PricingLogic.BUY_BUY_SELL) {
      return {
        tomeiBuyPrice:
          feedHistory.FeedBuyPrice *
            weight *
            (1 + adjustmentPercentage.adjustBuyPercentage / 100) +
          adjustmentPercentage.buyLabourCharges,
        tomeiSellPrice:
          feedHistory.FeedSellPrice *
            weight *
            (1 + adjustmentPercentage.adjustSellPercentage / 100) +
          adjustmentPercentage.sellLabourCharges,
        IsManualPriceActivatedYN: 'N',
      };
    } else {
      // Default pricing logic - use sell price for both calculations
      return {
        tomeiBuyPrice:
          feedHistory.FeedSellPrice *
            weight *
            (1 + adjustmentPercentage.adjustBuyPercentage / 100) +
          adjustmentPercentage.buyLabourCharges,
        tomeiSellPrice:
          feedHistory.FeedSellPrice *
            weight *
            (1 + adjustmentPercentage.adjustSellPercentage / 100) +
          adjustmentPercentage.sellLabourCharges,
        IsManualPriceActivatedYN: 'N',
      };
    }
  }

  public static async getLatestPrice(
    feedName: NonSourceFeedName,
    currency: string,
    companyCode: string = 'CTH',
    timestamp?: Date,
    dbTransaction?: any,
  ): Promise<ITomeiPriceHistoryAttr | null> {
    try {
      // If timestamp is provided, get the closest historical price
      if (timestamp) {
        return await TomeiPriceHistory.getClosestHistoricalPrice(
          feedName,
          currency,
          companyCode,
          timestamp,
          dbTransaction,
        );
      }

      // First try to get price from latest table
      const latestPrice = await TomeiPriceHistory.getFromLatestTable(
        feedName,
        currency,
        companyCode,
        dbTransaction,
      );
      if (latestPrice) return latestPrice;

      // Fallback to history table if latest not found
      const historicalPrice =
        await TomeiPriceHistory.getMostRecentHistoricalPrice(
          feedName,
          currency,
          companyCode,
          dbTransaction,
        );

      // If no price data exists, return null only for specific scenarios
      if (!historicalPrice) {
        return null;
      }

      return historicalPrice;
    } catch (error) {
      // Only catch database/connection errors, not data-not-found scenarios
      await ApplicationLog.warning({
        message: 'Non-critical error in getLatestPrice, returning null',
        details: {
          error: error.message,
          feedName,
          currency,
          companyCode,
          timestamp,
        },
        source: 'live-price',
        className: 'TomeiPriceHistory',
        methodName: 'getLatestPrice',
        transaction: dbTransaction,
      });

      if (
        error.name === 'SequelizeConnectionError' ||
        error.name === 'SequelizeDatabaseError' ||
        error.message?.includes('connection') ||
        error.message?.includes('database')
      ) {
        const classError = new ClassError(
          'TomeiPriceHistory',
          'DatabaseConnectionError',
          `Database error while retrieving latest price: ${error.message}`,
        );
        await TomeiPriceHistory.logger.error({
          error,
          methodName: 'getLatestPrice',
          transaction: dbTransaction,
        });
        throw classError;
      }
      return null;
    }
  }

  public static async getLatestPriceRequired(
    feedName: NonSourceFeedName,
    currency: string,
    companyCode: string = 'CTH',
    timestamp?: Date,
    dbTransaction?: any,
  ): Promise<ITomeiPriceHistoryAttr> {
    const latestPrice = await TomeiPriceHistory.getLatestPrice(
      feedName,
      currency,
      companyCode,
      timestamp,
      dbTransaction,
    );

    if (!latestPrice) {
      const error = new ClassError(
        'TomeiPriceHistory',
        'NoDataFoundError',
        `No price data found for feed ${feedName}, currency ${currency}, company ${companyCode}`,
      );
      await TomeiPriceHistory.logger.error({
        error,
        methodName: 'getLatestPriceRequired',
        transaction: dbTransaction,
      });
      throw error;
    }

    return latestPrice;
  }

  // Helper methods
  private static async getClosestHistoricalPrice(
    feedName: NonSourceFeedName,
    currency: string,
    companyCode: string,
    timestamp: Date,
    dbTransaction?: any,
  ): Promise<ITomeiPriceHistoryAttr> {
    try {
      const options = {
        where: {
          FeedName: feedName,
          Currency: currency,
          CompanyCode: companyCode,
        },
        order: [
          [
            Sequelize.literal(
              `ABS(TIMESTAMPDIFF(SECOND, DateTime, '${timestamp.toISOString()}'))`,
            ),
            'ASC',
          ],
        ],
        limit: 1,
        transaction: dbTransaction,
      };

      return await TomeiPriceHistory._Repo.findOne(options);
    } catch (error) {
      // Return null if there's any error retrieving the price data
      return null;
    }
  }

  private static async getFromLatestTable(
    feedName: NonSourceFeedName,
    currency: string,
    companyCode: string,
    dbTransaction?: any,
  ): Promise<ITomeiPriceHistoryAttr> {
    try {
      const latestPrice = await TomeiPriceHistory._LatestRepo.findOne({
        where: {
          FeedName: feedName,
          Currency: currency,
          CompanyCode: companyCode,
        },
        transaction: dbTransaction,
      });

      if (!latestPrice) return null;

      // Get additional fields from history record
      const tomeiPrice = await TomeiPriceHistory.init(
        dbTransaction,
        latestPrice.TomeiPriceHistoryId,
      );

      return {
        TomeiPriceHistoryId: latestPrice.TomeiPriceHistoryId,
        FeedHistoryId: tomeiPrice.FeedHistoryId,
        FeedName: latestPrice.FeedName,
        Currency: latestPrice.Currency,
        DateTime: latestPrice.DateTime,
        AdjustBuyPercentage: latestPrice.AdjustBuyPercentage,
        AdjustSellPercentage: latestPrice.AdjustSellPercentage,
        TomeiBuyPrice: latestPrice.TomeiBuyPrice,
        TomeiSellPrice: latestPrice.TomeiSellPrice,
        AdjustedById: latestPrice.AdjustedById,
        AdjustedDateTime: latestPrice.AdjustedDateTime,
        IsManualPriceActivatedYN: tomeiPrice.IsManualPriceActivatedYN,
        BuyLabourCharges: tomeiPrice.BuyLabourCharges,
        SellLabourCharges: tomeiPrice.SellLabourCharges,
        CompanyCode: latestPrice.CompanyCode, // Add CompanyCode from latest price
      };
    } catch (error) {
      // Return null if there's any error retrieving the price data
      return null;
    }
  }

  private static async getMostRecentHistoricalPrice(
    feedName: NonSourceFeedName,
    currency: string,
    companyCode: string,
    dbTransaction?: any,
  ): Promise<ITomeiPriceHistoryAttr> {
    try {
      const options = {
        where: {
          FeedName: feedName,
          Currency: currency,
          CompanyCode: companyCode,
        },
        order: [['DateTime', 'DESC']],
        limit: 1,
        transaction: dbTransaction,
      };

      return await TomeiPriceHistory._Repo.findOne(options);
    } catch (error) {
      // Return null if there's any error retrieving the price data
      return null;
    }
  }

  public static async findAll(
    page: number,
    row: number,
    feedName: NonSourceFeedName,
    search?: {
      DateTime: {
        startDate: Date;
        endDate: Date;
        withManualHistory?: boolean;
        withCutOffHistory?: boolean;
      };
      CompanyCode?: string;
    },
    dbTransaction?: any,
  ): Promise<{
    rows: ITomeiPriceHistoryFindAllAttr[];
    count: number;
  }> {
    try {
      const queryObj: any = {
        FeedName: feedName,
      };

      const options: any = {
        transaction: dbTransaction,
        limit: row,
        offset: row * (page - 1),
        order: [['DateTime', 'DESC']],
        include: [
          {
            model: FeedHistoryModel,
          },
        ],
      };

      if (search) {
        if (search.DateTime) {
          queryObj.DateTime = {
            [Op.between]: [search.DateTime.startDate, search.DateTime.endDate],
          };
        }

        if (search.CompanyCode) {
          queryObj.CompanyCode = search.CompanyCode;
        }
      }

      return await TomeiPriceHistory._Repo.findAllWithPagination({
        where: queryObj,
        ...options,
      });
    } catch (error) {
      await TomeiPriceHistory.logger.error({
        error,
        methodName: 'findAll',
        transaction: dbTransaction,
      });
      throw error;
    }
  }
}
