import mongoose from 'mongoose';
import type { OrganizerRepository } from './organizer.repository.js';
import type { RegimensService } from '../regimens/regimens.service.js';
import type { CabinetRepository } from '../cabinet/cabinet.repository.js';
import type { CabinetEventsService } from '../cabinet-events/cabinet-events.service.js';
import type { OrganizerFillInput, OrganizerFillQueryInput } from '@meshitrack/shared';
import {
  CabinetEventType,
  CabinetEventSourceType,
  OrganizerFillStatus,
  DosageFrequency,
  calculateQuantityNeeded,
} from '@meshitrack/shared';
import type { CreateCabinetEventData } from '../cabinet-events/cabinet-events.repository.js';
import { NotFoundError, BadRequestError } from '../../common/errors.js';

interface Deps {
  organizerRepository: OrganizerRepository;
  regimensService: RegimensService;
  cabinetRepository: CabinetRepository;
  cabinetEventsService: CabinetEventsService;
}

interface PreviewDeduction {
  cabinetItemId: string;
  expirationDate: string | null;
  quantityToTake: number;
  quantityBefore: number;
}

interface PreviewItem {
  medicineId: string;
  medicineName: string;
  quantityNeeded: number;
  quantityAvailable: number;
  isShort: boolean;
  shortage: number;
  cabinetBreakdown: PreviewDeduction[];
}

export class OrganizerService {
  private readonly organizerRepository: OrganizerRepository;
  private readonly regimensService: RegimensService;
  private readonly cabinetRepository: CabinetRepository;
  private readonly cabinetEventsService: CabinetEventsService;

  public constructor({
    organizerRepository,
    regimensService,
    cabinetRepository,
    cabinetEventsService,
  }: Deps) {
    this.organizerRepository = organizerRepository;
    this.regimensService = regimensService;
    this.cabinetRepository = cabinetRepository;
    this.cabinetEventsService = cabinetEventsService;
  }

  public async listFills(householdId: string, userId: string, query: OrganizerFillQueryInput) {
    return this.organizerRepository.findByHousehold(householdId, userId, query);
  }

  public async getFillById(id: string, householdId: string) {
    const fill = await this.organizerRepository.findById(id, householdId);
    if (!fill) throw new NotFoundError('Organizer fill not found');
    return fill;
  }

  public async preview(
    householdId: string,
    userId: string,
    regimenId: string,
    numberOfDays: number,
  ) {
    const regimen = await this.regimensService.getById(regimenId, householdId, userId);
    if (!regimen.isActive) {
      throw new BadRequestError('Regimen is not active');
    }

    const items: PreviewItem[] = [];
    let hasShortages = false;

    for (const med of regimen.medications) {
      if (med.frequency === DosageFrequency.AS_NEEDED) continue;

      const quantityNeeded = calculateQuantityNeeded(
        med.dosage,
        med.frequency as DosageFrequency,
        numberOfDays,
        med.customFrequencyPerDay ?? undefined,
      );

      // Get cabinet items for FEFO allocation
      const cabinetItems = await this.cabinetRepository.findActiveByMedicineForFEFO(
        householdId,
        med.medicineId,
      );

      let quantityAvailable = 0;
      const breakdown: PreviewDeduction[] = [];
      let remaining = quantityNeeded;

      for (const ci of cabinetItems) {
        if (remaining <= 0) break;
        const toTake = Math.min(remaining, ci.quantity);
        quantityAvailable += ci.quantity;
        breakdown.push({
          cabinetItemId: ci._id.toString(),
          expirationDate: ci.expirationDate ? ci.expirationDate.toISOString() : null,
          quantityToTake: toTake,
          quantityBefore: ci.quantity,
        });
        remaining -= toTake;
      }

      const isShort = remaining > 0;
      if (isShort) hasShortages = true;

      items.push({
        medicineId: med.medicineId,
        medicineName: med.medicineName,
        quantityNeeded,
        quantityAvailable,
        isShort,
        shortage: Math.max(0, remaining),
        cabinetBreakdown: breakdown,
      });
    }

    return {
      regimenName: regimen.name,
      numberOfDays,
      items,
      canFillCompletely: !hasShortages,
      hasShortages,
    };
  }

  public async fill(householdId: string, userId: string, input: OrganizerFillInput) {
    const previewResult = await this.preview(
      householdId,
      userId,
      input.regimenId,
      input.numberOfDays,
    );

    if (!input.allowPartial && previewResult.hasShortages) {
      throw new BadRequestError(
        'Not enough stock to fill completely. Use allowPartial=true to allow partial fills.',
      );
    }

    const regimen = await this.regimensService.getById(input.regimenId, householdId, userId);

    // Execute deductions within a transaction
    const session = await mongoose.startSession();
    const events: CreateCabinetEventData[] = [];
    const fillItems = [];

    try {
      session.startTransaction();

      for (const previewItem of previewResult.items) {
        const deductions = [];
        let totalTaken = 0;

        for (const bd of previewItem.cabinetBreakdown) {
          const updated = await this.cabinetRepository.adjustQuantity(
            bd.cabinetItemId,
            householdId,
            -bd.quantityToTake,
          );

          if (updated) {
            deductions.push({
              cabinetItemId: bd.cabinetItemId,
              quantityTaken: bd.quantityToTake,
            });
            totalTaken += bd.quantityToTake;

            events.push({
              householdId,
              userId,
              cabinetItemId: bd.cabinetItemId,
              medicineId: previewItem.medicineId,
              medicineName: previewItem.medicineName,
              eventType: CabinetEventType.CONSUMED,
              quantity: -bd.quantityToTake,
              quantityBefore: bd.quantityBefore,
              quantityAfter: bd.quantityBefore - bd.quantityToTake,
              sourceType: CabinetEventSourceType.ORGANIZER_FILL,
              sourceId: '', // Will be set after fill is created
            });
          }
        }

        const wasShort = totalTaken < previewItem.quantityNeeded;
        fillItems.push({
          medicineId: previewItem.medicineId,
          medicineName: previewItem.medicineName,
          quantityNeeded: previewItem.quantityNeeded,
          quantityTaken: totalTaken,
          wasShort,
          shortage: previewItem.quantityNeeded - totalTaken,
          deductions,
        });
      }

      const hasAnyShortage = fillItems.some((fi) => fi.wasShort);
      const fillRecord = await this.organizerRepository.create({
        householdId,
        userId,
        regimenId: input.regimenId,
        regimenName: regimen.name,
        numberOfDays: input.numberOfDays,
        fillDate: new Date(),
        items: fillItems,
        status: hasAnyShortage ? OrganizerFillStatus.PARTIAL : OrganizerFillStatus.COMPLETED,
        notes: input.notes,
      });

      await session.commitTransaction();

      // Set sourceId on events and log them
      const fillId = fillRecord._id.toString();
      for (const event of events) {
        event.sourceId = fillId;
      }
      await this.cabinetEventsService.logEvents(events);

      return fillRecord;
    } catch (err) {
      await session.abortTransaction();
      throw err;
    } finally {
      session.endSession();
    }
  }

  public async undoFill(householdId: string, fillId: string, userId: string) {
    const fill = await this.getFillById(fillId, householdId);

    if (fill.status === OrganizerFillStatus.REVERSED) {
      throw new BadRequestError('Fill has already been reversed');
    }

    const session = await mongoose.startSession();
    const events: CreateCabinetEventData[] = [];

    try {
      session.startTransaction();

      for (const item of fill.items) {
        for (const deduction of item.deductions) {
          // Get current quantity before restoring
          const current = await this.cabinetRepository.findById(
            deduction.cabinetItemId,
            householdId,
          );
          const quantityBefore = current?.quantity ?? 0;

          await this.cabinetRepository.adjustQuantity(
            deduction.cabinetItemId,
            householdId,
            deduction.quantityTaken,
          );

          events.push({
            householdId,
            userId,
            cabinetItemId: deduction.cabinetItemId,
            medicineId: item.medicineId,
            medicineName: item.medicineName,
            eventType: CabinetEventType.RESTORED,
            quantity: deduction.quantityTaken,
            quantityBefore,
            quantityAfter: quantityBefore + deduction.quantityTaken,
            sourceType: CabinetEventSourceType.ORGANIZER_UNDO,
            sourceId: fillId,
          });
        }
      }

      const updated = await this.organizerRepository.updateStatus(
        fillId,
        householdId,
        OrganizerFillStatus.REVERSED,
      );

      await session.commitTransaction();

      await this.cabinetEventsService.logEvents(events);

      if (!updated) throw new NotFoundError('Organizer fill not found');
      return updated;
    } catch (err) {
      await session.abortTransaction();
      throw err;
    } finally {
      session.endSession();
    }
  }
}
