/*
 * Copyright © 2024 Hexastack. All rights reserved.
 *
 * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
 * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
 * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
 */

import { Injectable } from '@nestjs/common';
import { EventEmitter2, OnEvent } from '@nestjs/event-emitter';

import { Subscriber } from '@/chat/schemas/subscriber.schema';
import { config } from '@/config';
import { LoggerService } from '@/logger/logger.service';
import { BaseService } from '@/utils/generics/base-service';

import { BotStatsRepository } from '../repositories/bot-stats.repository';
import { BotStats, BotStatsType } from '../schemas/bot-stats.schema';

@Injectable()
export class BotStatsService extends BaseService<BotStats> {
  constructor(
    readonly repository: BotStatsRepository,
    private readonly eventEmitter: EventEmitter2,
    private readonly logger: LoggerService,
  ) {
    super(repository);
  }

  /**
   * Retrieves statistics for messages within a specified time range and of specified types.
   *
   * @param from - The start date for filtering messages.
   * @param to - The end date for filtering messages.
   * @param types - An array of message types (of type BotStatsType) to filter the statistics.
   *
   * @returns A promise that resolves to an array of `BotStats` objects representing the message statistics.
   */
  async findMessages(
    from: Date,
    to: Date,
    types: BotStatsType[],
  ): Promise<BotStats[]> {
    return await this.repository.findMessages(from, to, types);
  }

  /**
   * Retrieves the most popular blocks within a specified time range.
   * Popular blocks are those triggered the most frequently.
   *
   * @param  from - The start date of the time range.
   * @param  to - The end date of the time range.
   * @returns A promise that resolves with an array of popular blocks, each containing an `id` and the number of times it was triggered (`value`).
   */
  async findPopularBlocks(
    from: Date,
    to: Date,
  ): Promise<{ id: string; value: number }[]> {
    return await this.repository.findPopularBlocks(from, to);
  }

  /**
   * Handles the event to track user activity and emit statistics for loyalty, returning users, and retention.
   *
   * This method checks the last visit of the subscriber and emits relevant analytics events
   * based on configured thresholds for loyalty, returning users, and retention.
   *
   * @param {Subscriber} subscriber - The subscriber object that contains last visit and retention data.
   */
  @OnEvent('hook:user:lastvisit')
  handleLastVisit(subscriber: Subscriber) {
    const now = +new Date();
    if (subscriber.lastvisit) {
      // A loyal subscriber is a subscriber that comes back after some inactivity
      if (now - +subscriber.lastvisit > config.analytics.thresholds.loyalty) {
        this.eventEmitter.emit(
          'hook:stats:entry',
          'returning_users',
          'Loyalty',
          subscriber,
        );
      }

      // Returning subscriber is a subscriber that comes back after some inactivity
      if (now - +subscriber.lastvisit > config.analytics.thresholds.returning) {
        this.eventEmitter.emit(
          'hook:stats:entry',
          'returning_users',
          'Returning users',
        );
      }
    }
    // Retention
    if (
      subscriber.retainedFrom &&
      now - +subscriber.retainedFrom > config.analytics.thresholds.retention
    ) {
      this.eventEmitter.emit(
        'hook:stats:entry',
        'retention',
        'Retentioned users',
      );
    }
  }

  /**
   * Handles the event to update bot statistics.
   *
   * @param type - The type of bot statistics being tracked (e.g., user messages, bot responses).
   * @param name - The name or identifier of the statistics entry (e.g., a specific feature or component being tracked).
   */
  @OnEvent('hook:stats:entry')
  async handleStatEntry(type: BotStatsType, name: string) {
    const day = new Date();
    day.setMilliseconds(0);
    day.setSeconds(0);
    day.setMinutes(0);
    day.setHours(0);

    try {
      const insight = await this.findOneOrCreate(
        { day: { $lte: day, $gte: day }, type, name },
        { day, type, name, value: 0 },
      );

      try {
        await this.updateOne(insight.id, { value: insight.value + 1 });
      } catch (err) {
        this.logger.error('Stats hook : Unable to update insight', err);
      }
    } catch (err) {
      this.logger.error('Stats hook : Unable to find or create insight', err);
    }
  }
}
