import { ClassError, ObjectBase } from '@tomei/general';
import { ApplicationLog } from '@tomei/log';
import { FeedHistoryRepository } from './feed-history.repository';
import { IFeedHistoryAttr } from '../../interfaces/price-feed-history-attr.interface';
import { Op, Sequelize } from 'sequelize';
import { SourceFeedName } from '../../enum/feed.enum';

export class FeedHistory extends ObjectBase implements IFeedHistoryAttr {
  ObjectId: string;
  ObjectName: string;
  TableName: 'price_FeedHistory';
  ObjectType: 'FeedHistory';
  FeedName: SourceFeedName;
  DateTime: Date;
  Currency: string;
  SourceFeedBuyPrice: number;
  SourceFeedSellPrice: number;
  FeedBuyPrice: number;
  FeedSellPrice: number;
  IsSourceFeedAvailableYN: string;
  IsManualPriceActivatedYN: string;
  IsManualCutOffYN: string;
  IsCircuitBreakerActivatedYN: string;

  private static logger = new ApplicationLog('live-price', 'FeedHistory');

  private static _Repo = new FeedHistoryRepository();

  get FeedHistoryId(): string {
    return this.ObjectId;
  }

  set FeedHistoryId(value: string) {
    this.ObjectId = value;
  }

  private constructor(feedHistoryAttr?: IFeedHistoryAttr) {
    super();
    if (feedHistoryAttr) {
      this.FeedHistoryId = feedHistoryAttr.FeedHistoryId;
      this.FeedName = feedHistoryAttr.FeedName;
      this.DateTime = feedHistoryAttr?.DateTime;
      this.Currency = feedHistoryAttr?.Currency;
      this.SourceFeedBuyPrice = feedHistoryAttr?.SourceFeedBuyPrice;
      this.SourceFeedSellPrice = feedHistoryAttr?.SourceFeedSellPrice;
      this.FeedBuyPrice = feedHistoryAttr?.FeedBuyPrice;
      this.FeedSellPrice = feedHistoryAttr?.FeedSellPrice;
      this.IsSourceFeedAvailableYN = feedHistoryAttr?.IsSourceFeedAvailableYN;
      this.IsManualPriceActivatedYN = feedHistoryAttr?.IsManualPriceActivatedYN;
      this.IsManualCutOffYN = feedHistoryAttr?.IsManualCutOffYN;
      this.IsCircuitBreakerActivatedYN =
        feedHistoryAttr?.IsCircuitBreakerActivatedYN;
    }
  }

  public static async init(dbTransaction: any, FeedHistoryId?: string) {
    try {
      if (FeedHistoryId) {
        const feedHistory = await FeedHistory._Repo.findByPk(
          FeedHistoryId,
          dbTransaction,
        );
        if (feedHistory) {
          return new FeedHistory(feedHistory);
        } else {
          const error = new ClassError(
            'FeedHistory',
            'FeedHistoryErrMsg00',
            'FeedHistory Not Found',
          );
          await FeedHistory.logger.error({
            error,
            methodName: 'init',
            transaction: dbTransaction,
          });
          throw error;
        }
      }
      return new FeedHistory();
    } catch (error) {
      throw new ClassError(
        'FeedHistory',
        'FeedHistoryErrMsg00',
        'FeedHistory To Initialize FeedHistory',
      );
    }
  }

  public async saveFeedPrice(priceData: IFeedHistoryAttr, dbTransaction: any) {
    try {
      const payload: IFeedHistoryAttr = {
        FeedHistoryId: this.createId(),
        ...priceData,
      };
      //Set the feed history object properties with the provided price data.
      this.FeedHistoryId = payload.FeedHistoryId;
      this.FeedName = payload.FeedName;
      this.DateTime = payload?.DateTime;
      this.Currency = payload?.Currency;
      this.SourceFeedBuyPrice = payload?.SourceFeedBuyPrice;
      this.SourceFeedSellPrice = payload?.SourceFeedSellPrice;
      this.FeedBuyPrice = payload?.FeedBuyPrice;
      this.FeedSellPrice = payload?.FeedSellPrice;
      this.IsSourceFeedAvailableYN = payload?.IsSourceFeedAvailableYN;
      this.IsManualCutOffYN = payload?.IsManualCutOffYN;
      this.IsCircuitBreakerActivatedYN = payload?.IsCircuitBreakerActivatedYN;
      this.IsManualPriceActivatedYN = payload?.IsManualPriceActivatedYN;
      const feedHistory = await FeedHistory._Repo.create(payload, {
        transaction: dbTransaction,
      });

      if (feedHistory) {
        return new FeedHistory(feedHistory);
      } else {
        const error = new ClassError(
          'FeedHistory',
          'FeedHistoryErrMsg01',
          'FeedHistory Not Created',
        );
        await FeedHistory.logger.error({
          error,
          methodName: 'saveFeedPrice',
          transaction: dbTransaction,
        });
        throw error;
      }
    } catch (error) {
      await FeedHistory.logger.error({
        error,
        methodName: 'saveFeedPrice',
        transaction: dbTransaction,
      });
      throw error;
    }
  }

  public static async getLatestPrice(
    feedName: string,
    currency: string,
    timestamp?: Date,
    dbTransaction?: any,
  ): Promise<IFeedHistoryAttr> {
    // Get latest price based on feedname, currency and optional timestamp
    try {
      // Part 1: Define Query Filter
      // Create a query filter to search the price_FeedHistory table, ensuring the query includes:
      // The feedname.
      // The currency.
      const queryFilter: any = {
        FeedName: feedName,
        Currency: currency,
      };
      const options: any = {
        where: queryFilter,
        order: [['DateTime', 'DESC']],
        limit: 1,
        transaction: dbTransaction,
      };
      // If a timestamp is provided, find records closest to the timestamp.
      // If no timestamp is provided, use the current timestamp for the search.
      if (timestamp) {
        options.order = [
          [
            Sequelize.literal(
              `ABS(TIMESTAMPDIFF(SECOND, DateTime, '${timestamp.toISOString()}'))`,
            ),
            'ASC',
          ],
        ];
      }
      // Part 2: Search for Price Record
      // Use the query filter to retrieve the closest matching price record.
      // If timestamp is provided, adjust the filter to find the record closest to the provided timestamp.
      // If no timestamp is provided, use the current timestamp and return the most recent record before or equal to this time.
      const feedHistory = await FeedHistory._Repo.findOne(options);
      // Part 3: Handle Response
      // If a matching record is found, return the record.
      // If no matching record is found:
      // If searching with a timestamp, return the message: "No price record found for the specified timestamp."
      // If searching with the current timestamp, return the message: "No latest price record found for the current timestamp."
      if (feedHistory) {
        return feedHistory;
      } else {
        return null;
      }
    } catch (error) {
      await FeedHistory.logger.error({
        error,
        methodName: 'getLatestPrice',
        transaction: dbTransaction,
      });
      throw error;
    }
  }

  public static async findAll(
    page: number,
    row: number,
    feedName: string,
    search?: {
      DateTime: {
        startDate: Date;
        endDate: Date;
      };
    },
    dbTransaction?: any,
  ): Promise<{
    rows: IFeedHistoryAttr[];
    count: number;
  }> {
    try {
      const queryObj: any = {
        FeedName: feedName,
      };

      const options: any = {
        transaction: dbTransaction,
        limit: row,
        offset: row * (page - 1),
        order: [['DateTime', 'DESC']],
      };

      if (search) {
        queryObj.DateTime = {
          [Op.between]: [search.DateTime.startDate, search.DateTime.endDate],
        };
      }

      return await FeedHistory._Repo.findAllWithPagination({
        where: queryObj,
        ...options,
      });
    } catch (error) {
      await FeedHistory.logger.error({
        error,
        methodName: 'getPaginatedHistory',
        transaction: dbTransaction,
      });
      throw error;
    }
  }
}
