import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { NotificationAudience, NotificationType } from '@edr/types';

import { NotificationInboxService } from '../notification-inbox/notification-inbox.service';
import { ContractsRepository } from './contracts.repository';

/**
 * How many days before a contract lapses the customer is reminded. Mirrored by
 * the portal contract list (EXPIRY_NOTICE_DAYS in contract-ui.tsx), which shows
 * the same countdown on the row.
 */
const EXPIRY_NOTICE_DAYS = 10;

/** Nightly sweep that flips contracts past contractValidUntil to EXPIRED. */
@Injectable()
export class ContractExpiryService {
  private readonly logger = new Logger(ContractExpiryService.name);

  constructor(
    private readonly contractsRepository: ContractsRepository,
    private readonly inbox: NotificationInboxService,
  ) {}

  /**
   * Warn every customer whose contract lapses in ~10 days, once. The repository
   * window is a rolling 24h slice, so a contract is picked up by exactly one
   * daily run — no reminded-flag column needed.
   *
   * ponytail: a missed run (API down over the slice) skips that contract's
   * reminder; the portal list still shows its countdown for the whole window.
   */
  @Cron(CronExpression.EVERY_DAY_AT_2AM, { name: 'contract-expiry-reminder' })
  async remindExpiringContracts(): Promise<void> {
    try {
      const expiring =
        await this.contractsRepository.findExpiringInDays(EXPIRY_NOTICE_DAYS);
      let notified = 0;
      for (const contract of expiring) {
        if (!contract.companyId || !contract.contractValidUntil) continue;
        const endsOn = contract.contractValidUntil.toLocaleDateString('en-GB');
        await this.inbox.notify({
          recipients: { companyId: contract.companyId },
          audience: NotificationAudience.PORTAL,
          type: NotificationType.CONTRACT_STATUS,
          title: `Contract ${contract.reference} expires in ${EXPIRY_NOTICE_DAYS} days`,
          body:
            `Your contract ${contract.reference} is valid until ${endsOn}. ` +
            'After that date it stops accepting new bookings — contact EDR if ' +
            'you need it renewed.',
          link: '/contracts',
          data: { contractId: contract.id, action: 'CONTRACT_EXPIRING' },
        });
        notified += 1;
      }
      this.logger.log(
        `Contract expiry reminder: ${notified} customer(s) warned of a contract ` +
          `lapsing in ${EXPIRY_NOTICE_DAYS} days`,
      );
    } catch (err) {
      // Never throws into the scheduler — a failed reminder must not stop the
      // expiry sweep from running.
      this.logger.error(
        `Contract expiry reminder failed: ${(err as Error).message}`,
        (err as Error).stack,
      );
    }
  }

  @Cron(CronExpression.EVERY_DAY_AT_1AM, { name: 'contract-expiry-sweep' })
  async expireLapsedContracts(): Promise<void> {
    try {
      const affected = await this.contractsRepository.expireLapsedContracts();
      this.logger.log(`Contract expiry sweep: ${affected} contract(s) marked EXPIRED`);
    } catch (err) {
      this.logger.error(
        `Contract expiry sweep failed: ${(err as Error).message}`,
        (err as Error).stack,
      );
      try {
        await this.inbox.notify({
          recipients: { allBackoffice: true },
          audience: NotificationAudience.BACKOFFICE,
          type: NotificationType.GENERIC,
          title: 'Contract expiry sweep failed',
          body: `The nightly job that expires lapsed contracts failed: ${(err as Error).message}. Contracts past their validity date may still show as active until this is fixed.`,
          data: { action: 'CONTRACT_EXPIRY_SWEEP_FAILED' },
        });
      } catch (notifyErr) {
        this.logger.error(
          `Contract expiry sweep failure alert also failed: ${(notifyErr as Error).message}`,
        );
      }
    }
  }
}
