import { NotificationService } from '@/modules/notification/notification.service';
import {
  BadRequestException,
  HttpStatus,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { InjectRepository } from '@nestjs/typeorm';
import { plainToClass } from 'class-transformer';
import { Response } from 'express';
import * as fs from 'fs';
import { existsSync, readdirSync } from 'fs';
import * as _ from 'lodash';
import { v4 as uuid } from 'uuid';
import {
  FilterOperator,
  FilterSuffix,
  paginate,
  PaginateQuery,
} from 'nestjs-paginate';
import { Column } from 'nestjs-paginate/lib/helper';
import { join } from 'path';
import AppResponse from 'src/response/app-response';
import {
  extractModelId,
  isTimePassedByMinutes,
  isTimeReached,
  parseVideoFileName,
  subtractMinutes,
} from 'src/ultils';
import { In, IsNull, Not, Repository } from 'typeorm';
import { ClientUpdateBidDto } from '../dto/bid/client-update-bid.dto';
import { ClientUpdateLoginStatusDto } from '../dto/bid/client-update-login-status.dto';
import { CreateBidDto } from '../dto/bid/create-bid.dto';
import { UpdateBidDto } from '../dto/bid/update-bid.dto';
import { UpdateStatusByPriceDto } from '../dto/bid/update-status-by-price.dto';
import { BidHistory } from '../entities/bid-history.entity';
import { BidMetadata } from '../entities/bid-metadata.entity';
import { Bid } from '../entities/bid.entity';
import { ImageCompressionPipe } from '../pipes/image-compression-pipe';
import { Constant } from '../utils/constant';
import { Event } from '../utils/events';
import { BidMetadatasService } from './bid-metadatas.service';
import { WebBidsService } from './web-bids.service';

@Injectable()
export class BidsService {
  constructor(
    @InjectRepository(Bid)
    readonly bidsRepo: Repository<Bid>,
    @InjectRepository(BidHistory)
    readonly bidHistoriesRepo: Repository<BidHistory>,
    private readonly webBidsService: WebBidsService,
    private readonly eventEmitter: EventEmitter2,
    private readonly notificationService: NotificationService,
    private readonly bidMetadatasService: BidMetadatasService,
  ) {}

  async index(query: PaginateQuery) {
    const filterableColumns: {
      [key in Column<Bid> | (string & {})]?:
        | (FilterOperator | FilterSuffix)[]
        | true;
    } = {
      id: true,
      model: [FilterOperator.ILIKE],
      lot_id: true,
      close_time: true,
      name: [FilterOperator.ILIKE],
      status: true,
    };

    query.filter = AppResponse.processFilters(query.filter, filterableColumns);

    const data = await paginate(query, this.bidsRepo, {
      sortableColumns: [
        'id',
        'close_time',
        'close_time_ts',
        'first_bid',
        'model',
        'lot_id',
        'max_price',
        'status',
        'name',
      ],
      searchableColumns: ['id', 'status', 'model', 'lot_id', 'name'],
      defaultLimit: 15,
      filterableColumns,
      defaultSortBy: [['id', 'DESC']],
      maxLimit: 100,
      relations: {
        histories: true,
        web_bid: true,
        metadata: true,
      },
    });

    return AppResponse.toPagination<Bid>(data, true, Bid);
  }

  async clientIndex() {
    const result = await this.bidsRepo.find({
      relations: { histories: true },
      where: { status: 'biding' },
    });

    return AppResponse.toResponse(result);
  }

  async emitAllBidEvent() {
    await this.webBidsService.emitAllBidEvent();
  }

  async create(data: CreateBidDto, response?: (result: Bid) => any) {
    const model = extractModelId(data.url);

    const prev = await this.bidsRepo.findOne({ where: { model: model } });

    if (prev) {
      throw new BadRequestException(
        AppResponse.toResponse(null, { message: 'Product is already exits' }),
      );
    }

    const webBid = await this.webBidsService.createByUrl(data.url);

    let metadata = BidMetadata.DEFAULT_META_DATA(webBid);

    if (data.metadata) {
      metadata = metadata.map((item) => {
        const reqData = data.metadata.find(
          (i) => i?.key_name === item.key_name,
        );

        if (!reqData) return { ...item };

        return {
          ...item,
          value:
            item.key_name === BidMetadata.MODE_KEY
              ? JSON.stringify(reqData.value)
              : reqData.value <= 0
                ? item.value
                : String(reqData.value),
        };
      });
    }

    const result = await this.bidsRepo.save({
      ...data,
      model,
      web_bid: webBid,
      metadata,
    });

    await this.emitAllBidEvent();

    const warnings = [];

    if (!webBid.username || !webBid.password) {
      // Add warning message
      warnings.push(
        `Account setup for ${webBid.origin_url} website is not yet complete.`,
      );
    }

    if (!webBid.active) {
      // Add warning message
      warnings.push(
        `${webBid.origin_url} is disabled. Please enable to continue.`,
      );
    }

    if (warnings.length) {
      // Send event warning
      this.eventEmitter.emit(Event.SEND_WARNING, {
        title: `System Warning: Abnormal Data Detected`,
        messages: warnings,
      });
    }

    // Send event success
    this.eventEmitter.emit(Event.SEND_SUCCESS, {
      title: 'Successfully Added Product',
      messages: [`Successfully added product to bid list: ${data.url}`],
    });

    return AppResponse.toResponse(
      response ? response(result) : plainToClass(Bid, result),
    );
  }

  async update(id: Bid['id'], { metadata = [], ...data }: UpdateBidDto) {
    const prev = await this.bidsRepo.findOne({
      where: { id },
      relations: { web_bid: true, metadata: true },
    });

    if (!prev) {
      throw new NotFoundException(
        AppResponse.toResponse(false, {
          message: 'Product not found',
          status_code: HttpStatus.NOT_FOUND,
        }),
      );
    }

    if (metadata) {
      await this.bidMetadatasService.bidMetadataRepo.upsert(
        metadata.map((item) => {
          return { ...item, bid: { id }, value: JSON.stringify(item.value) };
        }),
        ['id', 'bid', 'key_name'],
      );
    }

    let result = null;
    if (!prev.close_time) {
      // Trường hợp chưa có close_time => update đơn giản
      result = await this.bidsRepo.update(id, {
        ...data,
      });
    } else {
      // Trường hợp đã có close_time => kiểm tra arrival_offset_seconds có thay đổi không
      const arrival_offset_seconds =
        this.bidMetadatasService.getArrivalOffsetSecondsByMode(
          metadata as BidMetadata[],
        );
      const prev_arrival_offset_seconds =
        this.bidMetadatasService.getArrivalOffsetSecondsByMode(prev.metadata);

      if (arrival_offset_seconds !== prev_arrival_offset_seconds) {
        // Nếu offset thay đổi thì cần cập nhật lại start_bid_time
        const start_bid_time = arrival_offset_seconds
          ? subtractMinutes(prev.close_time, arrival_offset_seconds / 60)
          : prev.start_bid_time;

        result = await this.bidsRepo.update(id, {
          ...data,
          start_bid_time,
        });
      } else {
        result = await this.bidsRepo.update(id, {
          ...data,
        });
      }
    }

    if (!result) throw new BadRequestException(false);

    await this.emitAllBidEvent();

    return AppResponse.toResponse(true);
  }

  async getBidForClientUpdate(id: Bid['id']) {
    return await this.bidsRepo.findOne({
      where: { id },
      relations: { histories: true, web_bid: true, metadata: true },
      order: {
        histories: {
          price: 'DESC',
        },
      },
    });
  }

  async toggle(id: Bid['id']) {
    const bid = await this.bidsRepo.findOne({
      where: { id },
      relations: { web_bid: true },
    });

    if (!bid) {
      throw new NotFoundException(
        AppResponse.toResponse(false, {
          message: 'Product not found',
          status_code: HttpStatus.NOT_FOUND,
        }),
      );
    }

    if (bid.status === 'biding') {
      await this.bidsRepo.update(id, { status: 'out-bid' });

      this.emitAllBidEvent();

      // send message event
      this.notificationService.emitBidStatus({ ...bid, status: 'out-bid' });

      return AppResponse.toResponse(true);
    }

    const lastHistory = await this.bidHistoriesRepo.findOne({
      where: { bid: { id: bid.id } },
      order: {
        created_at: 'DESC',
      },
    });

    if (lastHistory && lastHistory.price + bid.plus_price > bid.max_price) {
      throw new BadRequestException(
        AppResponse.toResponse(false, { message: 'Price is out of Max Price' }),
      );
    }

    if (bid.close_time && isTimeReached(bid.close_time)) {
      throw new BadRequestException(
        AppResponse.toResponse(false, { message: 'Product was closed' }),
      );
    }

    await this.bidsRepo.update(id, { status: 'biding' });

    // send message event
    this.notificationService.emitBidStatus({ ...bid, status: 'biding' });

    this.emitAllBidEvent();
    return AppResponse.toResponse(true);
  }

  /**
   * Workflow
   * START
   *  |
   *  |--> Tìm bid theo id --> Không có? --> Throw 404
   *  |
   *  |--> Nếu chưa có thời gian bắt đầu --> Tính offset --> Gán start_time
   *  |
   *  |--> Nếu đã hết giờ:
   *  |     |--> Nếu outbid --> Gán status 'out-bid'
   *  |     |--> Else --> Gán status 'win-bid'
   *  |
   *  |--> Nếu chưa hết giờ:
   *  |     |--> Nếu vượt giới hạn --> Gán status 'out-bid'
   *  |
   *  |--> Nếu close_time mới > cũ --> cập nhật
   *  |--> Nếu có model mới và chưa có model --> gán
   *  |
   *  |--> Gọi `save(...)` để lưu lại DB
   *  |--> Nếu có metadata --> gọi `upsert`
   *  |
   *  |--> Gửi sự kiện emitAllBidEvent
   *  |--> Nếu status là out-bid hoặc win-bid --> gửi notification
   *  |
   * * |--> Trả response
   * END
   */
  async clientUpdate(
    id: Bid['id'],
    { close_time, model, metadata, ...data }: ClientUpdateBidDto, // Nhận dữ liệu cập nhật
  ) {
    // // Tìm kiếm phiên đấu giá trong database theo id
    // let bid = await this.bidsRepo.findOne({
    //   where: { id },
    //   relations: { histories: true, web_bid: true, metadata: true },
    //   order: {
    //     histories: {
    //       price: 'DESC',
    //     },
    //   },
    // });

    let bid = await this.getBidForClientUpdate(id);

    // Nếu không tìm thấy phiên đấu giá, trả về lỗi 404
    if (!bid)
      throw new NotFoundException(
        AppResponse.toResponse(null, {
          message: 'Not found bid',
          status_code: HttpStatus.NOT_FOUND,
        }),
      );

    // Nếu phiên đấu giá chưa có thời gian bắt đầu và kết thúc
    if (!bid.close_time && !bid.start_bid_time && data?.lot_id) {
      // Tách lấy arrival_offset_seconds trong product nếu không có lấy trong web bid.
      const arrival_offset_seconds =
        this.bidMetadatasService.getArrivalOffsetSecondsByMode(bid.metadata) ||
        bid.web_bid.arrival_offset_seconds;

      // Thiết lập thời gian bắt đầu là 5 phút trước khi đóng
      // bid.start_bid_time = new Date().toUTCString();
      bid.start_bid_time = subtractMinutes(
        close_time,
        arrival_offset_seconds / 60,
      );

      // update
      await this.bidsRepo.update(
        { id },
        { start_bid_time: bid.start_bid_time },
      );
    }

    // Cập nhật thời gian kết thúc đấu giá nếu `close_time` mới lớn hơn `close_time` cũ
    if (
      close_time &&
      new Date(close_time).getTime() > new Date(bid.close_time).getTime()
    ) {
      bid.close_time = close_time;
      bid.close_time_ts = new Date(close_time);

      // update
      await this.bidsRepo.update(
        { id },
        { close_time: bid.close_time, close_time_ts: bid.close_time_ts },
      );

      bid = await this.getBidForClientUpdate(id);
    }

    // Kiểm tra nếu thời gian đóng bid đã đạt tới (tức phiên đấu giá đã kết thúc)
    if (bid.close_time && isTimeReached(bid.close_time)) {
      const bidHistoriesItem = bid.histories[0]; // Lấy lịch sử bid gần nhất (mới nhất)

      if (
        !bidHistoriesItem ||
        // Nếu giá cuối cùng không phải là giá của người dùng và giá hiện tại vượt quá mức người dùng đặt + bước giá
        (bidHistoriesItem.price !== data.current_price &&
          data.current_price > bid.max_price + bid.plus_price)
      ) {
        bid.status = 'out-bid'; // Người dùng đã bị outbid khi đấu giá kết thúc
      } else {
        bid.status = 'win-bid'; // Người dùng là người thắng nếu không bị outbid
      }
    } else {
      // Nếu phiên đấu giá vẫn đang diễn ra và giá hiện tại vượt quá giới hạn đặt của người dùng
      if (
        data.current_price > bid.max_price + bid.plus_price ||
        (!bid.histories.length &&
          data.reserve_price > bid.max_price + bid.plus_price)
      ) {
        bid.status = 'out-bid'; // Gán trạng thái là đã bị outbid
      }
    }

    // // Cập nhật thời gian kết thúc đấu giá nếu `close_time` mới lớn hơn `close_time` cũ
    // if (
    //   close_time &&
    //   new Date(close_time).getTime() > new Date(bid.close_time).getTime()
    // ) {
    //   bid.close_time = close_time;
    //   bid.close_time_ts = new Date(close_time);
    // }

    // Nếu chưa có `model` nhưng dữ liệu mới có model, thì cập nhật model
    if (model && !bid.model) {
      bid.model = model;
    }

    // Lưu cập nhật vào database
    const result = await this.bidsRepo.save({
      ...bid,
      ...data,
      current_price: Math.max(data?.current_price || 0, bid.current_price),
      updated_at: new Date(), // Cập nhật timestamp
    });

    if (metadata) {
      await this.bidMetadatasService.upsert(metadata, bid);
    }

    // Phát sự kiện cập nhật toàn bộ danh sách đấu giá
    this.emitAllBidEvent();

    // send event message
    // Nếu trạng thái của bid là 'out-bid', gửi thông báo
    if (['out-bid', 'win-bid'].includes(result.status)) {
      this.notificationService.emitBidStatus(result);
    }

    // Send INFO
    if (bid.current_price < result.current_price && bid.histories.length) {
      this.eventEmitter.emit(Event.SEND_INFO, {
        title: 'New Higher Bid Detected',
        messages: [
          `Another user just placed a higher bid of ${result.current_price} on <a href="${bid.url}">${bid.name}</a>.`,
        ],
      });
    }

    // Send error when bidding fail
    if (
      isTimePassedByMinutes(result.start_bid_time, 1) &&
      !bid.histories.length &&
      !this.bidMetadatasService.isSandbox(bid.metadata)
    ) {
      this.eventEmitter.emit(Event.SEND_ERROR, {
        title: 'Bidding Error Detected',
        messages: [
          `An error occurred while placing a bid on <a href="${bid.url}">${bid.name}</a>. Please check the system.`,
        ],
      });
    }

    // Trả về kết quả cập nhật dưới dạng response chuẩn
    return AppResponse.toResponse(plainToClass(Bid, result));
  }

  async outBid(id: Bid['id']) {
    const result = await this.bidsRepo.update(id, { status: 'out-bid' });

    const bid = await this.bidsRepo.findOne({
      where: { id },
      relations: { web_bid: true },
    });

    if (!result) throw new BadRequestException(AppResponse.toResponse(false));

    await this.emitAllBidEvent();

    // send message event
    this.notificationService.emitBidStatus(bid);

    return AppResponse.toResponse(true);
  }

  async delete(id: Bid['id']) {
    const bid = await this.bidsRepo.findOne({ where: { id } });

    if (!bid)
      throw new NotFoundException(
        AppResponse.toResponse(false, {
          message: 'Bid is not found',
          status_code: HttpStatus.NOT_FOUND,
        }),
      );

    await this.bidsRepo.delete({ id: bid.id });

    if (bid.status === 'biding') {
      this.emitAllBidEvent();
    }

    return AppResponse.toResponse(true, { message: 'Delete success !' });
  }

  async deletes(ids: Bid['id'][]) {
    const result = await this.bidsRepo.delete({
      id: In(ids),
    });

    if (!result.affected) {
      throw new BadRequestException(
        AppResponse.toResponse(false, {
          message: 'No items have been deleted yet.',
          status_code: HttpStatus.BAD_REQUEST,
        }),
      );
    }

    this.emitAllBidEvent();

    return AppResponse.toResponse(true, { message: 'Delete success !' });
  }

  async updateStatusByPrice(id: Bid['id'], data: UpdateStatusByPriceDto) {
    const bid = await this.bidsRepo.findOne({
      where: { id },
      relations: { web_bid: true },
    });

    if (!bid)
      throw new NotFoundException(
        AppResponse.toResponse(false, {
          message: 'Bid is not found',
          status_code: HttpStatus.NOT_FOUND,
        }),
      );

    if (!isTimeReached(bid.close_time)) {
      throw new BadRequestException(
        AppResponse.toResponse(false, { message: 'Product is opening' }),
      );
    }

    const lastHistory = await this.bidHistoriesRepo.findOne({
      where: { bid: { id: bid.id } },
      order: { created_at: 'DESC' },
    });

    if (lastHistory && lastHistory.price === data.current_price) {
      if (bid.status !== 'win-bid') {
        await this.bidsRepo.update(bid.id, { status: 'win-bid' });

        // send event message
        this.notificationService.emitBidStatus({
          ...bid,
          status: 'win-bid',
        });
      }
    } else {
      if (bid.status !== 'out-bid') {
        await this.bidsRepo.update(bid.id, { status: 'out-bid' });

        // send event message
        this.notificationService.emitBidStatus({
          ...bid,
          status: 'out-bid',
        });
      }
    }

    this.emitAllBidEvent();
    return AppResponse.toResponse(true);
  }

  async updateStatusWork(
    id: Bid['id'],
    type: string,
    image: Express.Multer.File,
  ) {
    if (!image) {
      throw new BadRequestException(
        AppResponse.toResponse(null, { message: 'File or Url is required' }),
      );
    }

    const data: { filename: string } | undefined =
      await new ImageCompressionPipe(
        `${Constant.WORK_IMAGES_FOLDER}/${type.replaceAll('_', '-').toLocaleLowerCase()}/${id}`,
        {},
        {
          unique_image_folder: true,
          unique_name: true,
        },
      ).transform(image);

    if (!data)
      throw new BadRequestException(
        AppResponse.toResponse(null, {
          message: "Can't create media",
          status_code: HttpStatus.BAD_REQUEST,
        }),
      );

    // update time snapshot for API BID
    if (type === 'API_BID') {
      this.webBidsService.webBidRepo.update(id, { snapshot_at: new Date() });
    }

    this.eventEmitter.emit(`working`, {
      status: 're-update',
      id,
      type,
      filename: data.filename,
    });

    return AppResponse.toResponse(true);
  }

  async uploadRecord(id: Bid['id'], video: Express.Multer.File) {
    if (!video) {
      throw new BadRequestException(
        AppResponse.toResponse(null, { message: 'File or Url is required' }),
      );
    }

    const data: { filename: string } | undefined =
      await new ImageCompressionPipe(
        `${Constant.RECORD_FOLDER}`,
        {},
        {
          unique_image_folder: false,
          unique_name: false,
        },
      ).transform(video);

    if (!data)
      throw new BadRequestException(
        AppResponse.toResponse(null, {
          message: "Can't create media",
          status_code: HttpStatus.BAD_REQUEST,
        }),
      );

    return AppResponse.toResponse(true);
  }

  async getStatusWorkingImage(
    id: Bid['id'],
    type: string,
    name: string,
    res: Response,
  ) {
    const rootDir = process.cwd();
    const folderPath = join(
      rootDir,
      `${Constant.MEDIA_PATH}/${Constant.WORK_IMAGES_FOLDER}/${type}/${id}`,
    );

    if (!existsSync(folderPath)) {
      throw new NotFoundException(
        AppResponse.toResponse(null, {
          message: 'Folder not found',
          status_code: HttpStatus.NOT_FOUND,
        }),
      );
    }

    let filePath: string;

    if (name === Event.WORKING) {
      const files = readdirSync(folderPath).filter((file) =>
        /\.(jpg|jpeg|png|webp)$/i.test(file),
      );

      if (files.length === 0) {
        throw new NotFoundException(
          AppResponse.toResponse(null, {
            message: 'No images found',
            status_code: HttpStatus.NOT_FOUND,
          }),
        );
      }

      // Lấy ảnh đầu tiên trong danh sách
      filePath = join(folderPath, files[0]);
    } else {
      // Nếu name khác 'working', lấy file cụ thể
      filePath = join(folderPath, `${name}`);

      if (!existsSync(filePath)) {
        throw new NotFoundException(
          AppResponse.toResponse(null, {
            message: 'Image not found',
            status_code: HttpStatus.NOT_FOUND,
          }),
        );
      }
    }

    return res.sendFile(filePath);
  }

  async getRecord(name: string, res: Response) {
    const rootDir = process.cwd();
    const filePath = join(
      rootDir,
      `${Constant.MEDIA_PATH}/${Constant.RECORD_FOLDER}/${name}`,
    );

    if (!existsSync(filePath)) {
      throw new NotFoundException(
        AppResponse.toResponse(null, {
          message: 'Folder not found',
          status_code: HttpStatus.NOT_FOUND,
        }),
      );
    }

    const stat = fs.statSync(filePath);
    const fileSize = stat.size;

    res.writeHead(200, {
      'Content-Type': 'video/mp4',
      'Content-Length': fileSize,
    });

    const readStream = fs.createReadStream(filePath);
    readStream.pipe(res);
  }

  async getRecords(id: Bid['id']) {
    const rootDir = process.cwd();
    const folderPath = join(
      rootDir,
      `${Constant.MEDIA_PATH}/${Constant.RECORD_FOLDER}`,
    );

    if (!existsSync(folderPath)) {
      throw new NotFoundException(
        AppResponse.toResponse(null, {
          message: 'Folder not found',
          status_code: HttpStatus.NOT_FOUND,
        }),
      );
    }

    const files = await fs.promises.readdir(folderPath);

    const data = files
      .map((item) => {
        return parseVideoFileName(item);
      })
      .filter((i) => i.bid_id == id);

    const sorted = _.orderBy(data, ['timestamp'], ['desc']);

    return AppResponse.toResponse(sorted);
  }

  async deleteRecord(name: string) {
    const rootDir = process.cwd();
    const filePath = join(
      rootDir,
      `${Constant.MEDIA_PATH}/${Constant.RECORD_FOLDER}/${name}`,
    );

    if (!existsSync(filePath)) {
      throw new NotFoundException(
        AppResponse.toResponse(null, {
          message: 'File not found',
          status_code: HttpStatus.NOT_FOUND,
        }),
      );
    }

    await fs.promises.unlink(filePath);

    return AppResponse.toResponse(true);
  }

  async getImagesWorking(id: Bid['id'], type: string) {
    const rootDir = process.cwd();
    const folderPath = join(
      rootDir,
      `${Constant.MEDIA_PATH}/${Constant.WORK_IMAGES_FOLDER}/${type}/${id}`,
    );

    if (!existsSync(folderPath)) {
      throw new NotFoundException(
        AppResponse.toResponse(null, {
          message: 'Folder not found',
          status_code: HttpStatus.NOT_FOUND,
        }),
      );
    }

    // Lấy danh sách file trong folderPath
    const files = readdirSync(folderPath);

    return AppResponse.toResponse(files);
  }

  async emitLoginStatus(data: ClientUpdateLoginStatusDto) {
    this.eventEmitter.emit(Event.statusLogin(data.data), data);

    return AppResponse.toResponse(true);
  }

  async getNextBid(): Promise<Bid | null> {
    const all = await this.bidsRepo.find({
      where: { status: 'biding', close_time: Not(IsNull()) },
      relations: { web_bid: true },
    });

    const now = Date.now();

    let nextBid = null;
    let minDiff = Infinity;

    for (const bid of all) {
      const time = Date.parse(bid.close_time);
      if (!isNaN(time) && time >= now) {
        const diff = time - now;
        if (diff < minDiff) {
          minDiff = diff;
          nextBid = bid;
        }
      }
    }

    return nextBid;
  }

  async getBidByModel(model: string) {
    const bid = await this.bidsRepo.findOne({
      where: { model },
      relations: { metadata: true, web_bid: true },
      select: {
        web_bid: { arrival_offset_seconds: true, early_tracking_seconds: true },
      },
    });

    if (!bid)
      return AppResponse.toResponse(null, {
        status_code: HttpStatus.NOT_FOUND,
      });

    return AppResponse.toResponse(plainToClass(Bid, bid));
  }

  async hookAction(
    { id, type }: { id: Bid['id']; type: 'action' | 'api' },
    data: any,
  ) {
    const bid = await this.bidsRepo.findOne({
      where: { id },
      relations: { metadata: true },
    });

    if (!bid)
      throw new NotFoundException(
        AppResponse.toResponse(null, { message: 'Not foud bid' }),
      );

    if (type === 'api') {
      const result = JSON.parse(data?.data || {});

      result['timestamp'] = new Date().getTime();
      result['uuid'] = uuid();

      const prevDemoResponse = this.bidMetadatasService.getDemoResponse(
        bid.metadata,
      );

      console.log({ prevDemoResponse, result });

      await this.bidMetadatasService.upsert(
        {
          [BidMetadata.DEMO_RESPONSE]:
            JSON.stringify([result, ...(prevDemoResponse || [])]) ||
            JSON.stringify([]),
        },
        bid,
      );
    } else {
      const recordUrl = data?.record_url || '';

      await this.bidMetadatasService.upsert(
        {
          [BidMetadata.LATEST_RECROD_LINK]: JSON.stringify(recordUrl),
        },
        bid,
      );
    }

    const latestBidData = await this.bidsRepo.findOne({
      where: { id },
      relations: { metadata: true },
    });

    this.eventEmitter.emit(Event.BID_DEMO, { bid: latestBidData, type });

    return AppResponse.toResponse(data);
  }
}
