import config from 'config';
import { Op } from 'sequelize';

import { ScheduledTask } from '@tamanu/shared/tasks';
import { log } from '@tamanu/shared/services/logging';
import { sleepAsync } from '@tamanu/utils/sleepAsync';
import { InvalidConfigError } from '.';

export class DeceasedPatientDischarger extends ScheduledTask {
  getName() {
    return 'DeceasedPatientDischarger';
  }

  constructor(context) {
    const conf = config.schedules.deceasedPatientDischarger;
    const { schedule, jitterTime, enabled } = conf;
    super(schedule, log, jitterTime, enabled);
    this.config = conf;
    this.models = context.store.models;
  }

  async run() {
    const { Encounter, Patient } = this.models;

    const query = {
      where: {
        endDate: null,
        '$patient.date_of_death$': { [Op.not]: null },
      },
      include: [
        {
          model: Patient,
          as: 'patient',
        },
      ],
    };

    const toProcess = await Encounter.count(query);
    if (toProcess === 0) return;

    const { batchSize, batchSleepAsyncDurationInMilliseconds } = this.config;

    // Make sure these exist, else they will prevent the script from working
    if (!batchSize || !batchSleepAsyncDurationInMilliseconds) {
      throw new InvalidConfigError(
        'batchSize and batchSleepAsyncDurationInMilliseconds must be set for DeceasedPatientDischarger',
      );
    }

    log.info(
      `Auto-discharging ${toProcess} encounters for deceased patients (${batchSize} records per batch)`,
    );

    // Keyset pagination: discharged encounters get an endDate and leave the
    // filter, so an advancing offset would slide past matching rows. Paging
    // by id is stable whether or not each row leaves the result set.
    let lastSeenId = '';
    // eslint-disable-next-line no-constant-condition
    while (true) {
      const encounters = await Encounter.findAll({
        ...query,
        where: {
          ...query.where,
          id: { [Op.gt]: lastSeenId },
        },
        limit: batchSize,
        order: [['id', 'ASC']],
      });
      if (encounters.length === 0) break;
      lastSeenId = encounters[encounters.length - 1].id;

      for (const encounter of encounters) {
        const patient = await encounter.getPatient();
        if (!patient.dateOfDeath) {
          log.warn(`Deceased patient ${patient.id} not actually deceased! Skipping...`);
          continue;
        }

        const [patientDeathData] = await patient.getDeathData();

        if (!patientDeathData) {
          log.warn(`Deceased patient ${patient.id} has no death data! Skipping...`);
          continue;
        }

        const discharger = await patientDeathData.getClinician();

        if (!discharger) {
          log.warn(`Deceased patient ${patient.id} has no death data clinician! Skipping...`);
          continue;
        }

        await encounter.update({
          endDate: patient.dateOfDeath,
          systemNote: 'Automatically discharged',
          discharge: {
            dischargerId: discharger.id,
            note: 'Automatically discharged by deceased patient discharger',
          },
        });
        log.info(
          `Auto-closed encounter with id ${encounter.id} (discharger=${discharger.id}, dod=${patient.dateOfDeath})`,
        );
      }

      await sleepAsync(batchSleepAsyncDurationInMilliseconds);
    }
  }
}
