import { Transaction } from 'sequelize';
import cuid from '../../helpers/cuid';
import { TrigerredByEnum } from '../../enum/triggered-by.enum';
import { EventTypeEnum } from '../../enum/event-type.enum';
import { SyncLoggerRepository } from './sync-logger.repository';
import { SyncStatusEnum } from '../../enum/sync-status.enum';
import { SystemSyncPolicy } from '../system-sync-policy';
import {
  IsPolicyAllowInsertEnum,
  IsPolicyAllowUpdateEnum,
} from '../../enum/system-sync-policy.enum';
import { syncQueue } from '../sync-queue/sync-queue';

interface LogSyncTargetsParams {
  CustomerId: string;
  SourceSystemCode: string;
  TargetSystemCodes: string[];
  TriggeredBy: TrigerredByEnum;
  EventType: EventTypeEnum;
  dbTransaction: Transaction;
}

export class SyncLogger {
  private static readonly _Repository = new SyncLoggerRepository();

  async logSyncTargets(params: LogSyncTargetsParams): Promise<void> {
    const {
      CustomerId,
      SourceSystemCode,
      TargetSystemCodes,
      TriggeredBy,
      EventType,
      dbTransaction,
    } = params;

    for (const targetSystemCode of TargetSystemCodes) {
      try {
        if (targetSystemCode === SourceSystemCode) continue;

        const policy = await SystemSyncPolicy.get(
          SourceSystemCode,
          targetSystemCode,
          EventType,
        );

        const isAllowed =
          !!policy &&
          !(
            (EventType === EventTypeEnum.Insert &&
              policy.AllowInsertYN !== IsPolicyAllowInsertEnum.Y) ||
            (EventType === EventTypeEnum.Update &&
              policy.AllowUpdateYN !== IsPolicyAllowUpdateEnum.Y)
          );

        if (!isAllowed) {
          console.info('[SyncLogger.logSyncTargets] skipped by policy', {
            CustomerId,
            SourceSystemCode,
            TargetSystemCode: targetSystemCode,
            EventType,
          });
          continue;
        }

        const logEntry = {
          SyncLogId: cuid(),
          CustomerId,
          SourceSystemCode,
          TargetSystemCode: targetSystemCode,
          EventType,
          TrigerredBy: TriggeredBy,
          Status: SyncStatusEnum.Pending,
          CreatedAt: new Date(),
          UpdatedAt: new Date(),
        };

        await SyncLogger._Repository.create(logEntry, dbTransaction);

        await this.enqueueForLog(
          logEntry.SyncLogId,
          CustomerId,
          targetSystemCode,
          dbTransaction,
        );
      } catch (err) {
        console.error('[SyncLogger.logSyncTargets] outer failure', {
          CustomerId,
          SourceSystemCode,
          EventType,
          error: (err as Error)?.message,
        });
        throw err;
      }
    }
  }

  private async enqueueForLog(
    syncLogId: string,
    customerId: string,
    targetSystemCode: string,
    dbTransaction?: Transaction,
  ): Promise<void> {
    const doEnqueue = async () => {
      try {
        const job = await syncQueue.add('syncCustomer', {
          syncLogId,
          customerId,
          targetSystemCode,
        });

        // update created CustomerSyncLog record to write back BullMQ job id
        await SyncLogger._Repository.update(
          {
            QueueJobId: String(job.id),
            LastStatus: SyncStatusEnum.Pending,
            LastAttemptAt: new Date(),
          },
          {
            where: { SyncLogId: syncLogId },
          },
        );
      } catch (err) {
        await SyncLogger._Repository.update(
          { LastErrorMessage: String(err), LastAttemptAt: new Date() },
          { where: { SyncLogId: syncLogId } },
        );
        console.error('[SyncLogger.logSyncTargets] enqueue failed', {
          SyncLogId: syncLogId,
          error: (err as Error)?.message,
        });
      }
    };

    if (dbTransaction) {
      dbTransaction.afterCommit(() => {
        void doEnqueue();
      });
    } else {
      await doEnqueue();
    }
  }

  async markSuccess(
    syncLogId: string,
    transaction?: Transaction,
  ): Promise<void> {
    try {
      await SyncLogger._Repository.update(
        {
          QueueJobId: null,
          Status: SyncStatusEnum.Success,
          LastStatus: SyncStatusEnum.Success,
          SyncedAt: new Date(),
          LastAttemptAt: new Date(),
          LastErrorMessage: null,
        },
        { where: { SyncLogId: syncLogId }, transaction },
      );
    } catch (err) {
      console.error('[SyncLogger.markSuccess] failed', {
        syncLogId,
        error: (err as Error)?.message,
      });
      throw err;
    }
  }

  async markFailure(
    syncLogId: string,
    error: string,
    transaction?: Transaction,
  ): Promise<void> {
    try {
      const record = await SyncLogger._Repository.findByPk(
        syncLogId,
        transaction,
      );
      const currentRetryCount = record?.RetryCount ?? 0;

      await SyncLogger._Repository.update(
        {
          QueueJobId: null,
          Status: SyncStatusEnum.Failed,
          LastStatus: SyncStatusEnum.Failed,
          LastAttemptAt: new Date(),
          LastErrorMessage: error,
          RetryCount: currentRetryCount + 1,
        },
        { where: { SyncLogId: syncLogId }, transaction },
      );
    } catch (err) {
      console.error('[SyncLogger.markFailure] failed', {
        syncLogId,
        incomingError: error,
        error: (err as Error)?.message,
      });
      throw err;
    }
  }

  async markSkipped(
    syncLogId: string,
    reason: string,
    transaction?: Transaction,
  ): Promise<void> {
    try {
      await SyncLogger._Repository.update(
        {
          QueueJobId: null,
          Status: SyncStatusEnum.Skipped,
          LastStatus: SyncStatusEnum.Skipped,
          LastAttemptAt: new Date(),
          LastErrorMessage: reason,
        },
        { where: { SyncLogId: syncLogId }, transaction },
      );
    } catch (err) {
      console.error('[SyncLogger.markSkipped] failed', {
        syncLogId,
        reason,
        error: (err as Error)?.message,
      });
      throw err;
    }
  }
}
