import type { RegimensRepository } from './regimens.repository.js';
import type { MedicinesRepository } from '../medicines/medicines.repository.js';
import type { CabinetRepository } from '../cabinet/cabinet.repository.js';
import type { CabinetEventsService } from '../cabinet-events/cabinet-events.service.js';
import type { CreateRegimenInput, UpdateRegimenInput, RegimenQueryInput } from '@meshitrack/shared';
import { getFrequencyMultiplier } from '@meshitrack/shared';
import type { DosageFrequency } from '@meshitrack/shared';
import { NotFoundError } from '../../common/errors.js';

interface Deps {
  regimensRepository: RegimensRepository;
  medicinesRepository: MedicinesRepository;
  cabinetRepository: CabinetRepository;
  cabinetEventsService: CabinetEventsService;
}

export class RegimensService {
  private readonly regimensRepository: RegimensRepository;
  private readonly medicinesRepository: MedicinesRepository;
  private readonly cabinetRepository: CabinetRepository;
  private readonly cabinetEventsService: CabinetEventsService;

  public constructor({
    regimensRepository,
    medicinesRepository,
    cabinetRepository,
    cabinetEventsService,
  }: Deps) {
    this.regimensRepository = regimensRepository;
    this.medicinesRepository = medicinesRepository;
    this.cabinetRepository = cabinetRepository;
    this.cabinetEventsService = cabinetEventsService;
  }

  public async list(householdId: string, userId: string, query: RegimenQueryInput) {
    return this.regimensRepository.findByHousehold(householdId, userId, query);
  }

  public async getById(id: string, householdId: string, userId: string) {
    const regimen = await this.regimensRepository.findById(id, householdId, userId);
    if (!regimen) throw new NotFoundError('Regimen not found');
    return regimen;
  }

  public async create(data: CreateRegimenInput, householdId: string, userId: string) {
    const medications = await this.denormalizeMedications(data.medications, householdId);
    return this.regimensRepository.create(
      { name: data.name, isActive: data.isActive, medications },
      householdId,
      userId,
      userId,
    );
  }

  public async update(id: string, householdId: string, userId: string, data: UpdateRegimenInput) {
    await this.getById(id, householdId, userId);

    const updateData: Record<string, unknown> = {};
    if (data.name !== undefined) updateData['name'] = data.name;
    if (data.isActive !== undefined) updateData['isActive'] = data.isActive;
    if (data.medications !== undefined) {
      updateData['medications'] = await this.denormalizeMedications(data.medications, householdId);
    }

    const updated = await this.regimensRepository.update(id, householdId, userId, updateData);
    if (!updated) throw new NotFoundError('Regimen not found');
    return updated;
  }

  public async delete(id: string, householdId: string, userId: string) {
    await this.getById(id, householdId, userId);
    const deleted = await this.regimensRepository.softDelete(id, householdId, userId);
    if (!deleted) throw new NotFoundError('Regimen not found');
    return deleted;
  }

  public async getActiveByUser(householdId: string, userId: string) {
    return this.regimensRepository.findActiveByUser(householdId, userId);
  }

  public async calculateBurnRates(householdId: string, userId: string) {
    const regimens = await this.regimensRepository.findActiveByUser(householdId, userId);

    // Sum daily consumption per medicine across all active regimens
    const consumptionMap = new Map<string, { medicineName: string; dailyConsumption: number }>();

    for (const regimen of regimens) {
      for (const med of regimen.medications) {
        const multiplier = getFrequencyMultiplier(
          med.frequency as DosageFrequency,
          med.customFrequencyPerDay ?? undefined,
        );
        const dailyDose = med.dosage * multiplier;
        const existing = consumptionMap.get(med.medicineId);
        if (existing) {
          existing.dailyConsumption += dailyDose;
        } else {
          consumptionMap.set(med.medicineId, {
            medicineName: med.medicineName,
            dailyConsumption: dailyDose,
          });
        }
      }
    }

    // Exclude medicines with zero daily consumption (e.g., AS_NEEDED frequency)
    for (const [id, consumption] of consumptionMap) {
      if (consumption.dailyConsumption === 0) consumptionMap.delete(id);
    }

    if (consumptionMap.size === 0) return [];

    // Get cabinet summary for all medicines in regimens
    const summaryResults = await this.cabinetRepository.getAggregateSummary(householdId);
    const stockMap = new Map<string, { totalQuantity: number; earliestExpiry: Date | null }>();
    for (const s of summaryResults) {
      stockMap.set(s._id as string, {
        totalQuantity: s.totalQuantity as number,
        earliestExpiry: (s.earliestExpiry as Date | null) ?? null,
      });
    }

    // Get average unit prices from purchase events
    const medicineIds = [...consumptionMap.keys()];
    const priceMap = await this.cabinetEventsService.getAvgUnitPrices(householdId, medicineIds);

    // Build burn rate array
    const burnRates = [];
    for (const [medicineId, consumption] of consumptionMap) {
      const stock = stockMap.get(medicineId);
      const totalInCabinet = stock?.totalQuantity ?? 0;
      const earliestExpiry = stock?.earliestExpiry ?? null;
      const dailyConsumption = consumption.dailyConsumption;

      /* v8 ignore next */
      const daysUntilEmpty =
        dailyConsumption > 0 ? Math.floor(totalInCabinet / dailyConsumption) : null;

      const priceData = priceMap.get(medicineId);
      const avgUnitPrice = priceData?.avgUnitPrice ?? null;
      const currency = priceData?.currency ?? null;
      const projectedDailyCost =
        avgUnitPrice !== null && dailyConsumption > 0 ? avgUnitPrice * dailyConsumption : null;

      burnRates.push({
        medicineId,
        medicineName: consumption.medicineName,
        dailyConsumption,
        totalInCabinet,
        daysUntilEmpty,
        earliestExpiry: earliestExpiry ? earliestExpiry.toISOString() : null,
        avgUnitPrice,
        projectedDailyCost,
        projectedMonthlyCost: projectedDailyCost !== null ? projectedDailyCost * 30 : null,
        projectedYearlyCost: projectedDailyCost !== null ? projectedDailyCost * 365 : null,
        currency,
      });
    }

    // Sort by daysUntilEmpty ASC (most urgent first, nulls last)
    /* v8 ignore next 6 */
    burnRates.sort((a, b) => {
      if (a.daysUntilEmpty === null && b.daysUntilEmpty === null)
        return a.medicineName.localeCompare(b.medicineName);
      if (a.daysUntilEmpty === null) return 1;
      if (b.daysUntilEmpty === null) return -1;
      const diff = a.daysUntilEmpty - b.daysUntilEmpty;
      return diff !== 0 ? diff : a.medicineName.localeCompare(b.medicineName);
    });

    return burnRates;
  }

  private async denormalizeMedications(
    medications: CreateRegimenInput['medications'],
    householdId: string,
  ) {
    const result = [];
    for (const med of medications) {
      const medicine = await this.medicinesRepository.findById(med.medicineId, householdId);
      if (!medicine) {
        throw new NotFoundError(`Medicine not found: ${med.medicineId}`);
      }
      result.push({
        medicineId: med.medicineId,
        medicineName: medicine.name,
        medicineStrength: medicine.strength,
        medicineStrengthUnit: medicine.strengthUnit,
        medicineForm: medicine.form,
        dosage: med.dosage,
        dosageUnit: med.dosageUnit,
        frequency: med.frequency,
        customFrequencyPerDay: med.customFrequencyPerDay,
        timeOfDay: med.timeOfDay,
        instructions: med.instructions,
      });
    }
    return result;
  }
}
