import type { RefillsRepository } from './refills.repository.js';
import type { RegimensService } from '../regimens/regimens.service.js';
import type { CabinetRepository } from '../cabinet/cabinet.repository.js';
import type { CabinetService } from '../cabinet/cabinet.service.js';
import type { MedicinePricesRepository } from '../medicine-prices/medicine-prices.repository.js';
import type { PurchasesRepository } from '../purchases/purchases.repository.js';
import type {
  CreateRefillListInput,
  UpdateRefillListInput,
  UpdateRefillListItemInput,
  RefillListQueryInput,
} from '@meshitrack/shared';
import { RefillListStatus } from '@meshitrack/shared';
import { NotFoundError } from '../../common/errors.js';

interface Deps {
  refillsRepository: RefillsRepository;
  regimensService: RegimensService;
  cabinetRepository: CabinetRepository;
  cabinetService: CabinetService;
  medicinePricesRepository: MedicinePricesRepository;
  purchasesRepository: PurchasesRepository;
}

export class RefillsService {
  private readonly refillsRepository: RefillsRepository;
  private readonly regimensService: RegimensService;
  private readonly cabinetRepository: CabinetRepository;
  private readonly cabinetService: CabinetService;
  private readonly medicinePricesRepository: MedicinePricesRepository;
  private readonly purchasesRepository: PurchasesRepository;

  public constructor({
    refillsRepository,
    regimensService,
    cabinetRepository,
    cabinetService,
    medicinePricesRepository,
    purchasesRepository,
  }: Deps) {
    this.refillsRepository = refillsRepository;
    this.regimensService = regimensService;
    this.cabinetRepository = cabinetRepository;
    this.cabinetService = cabinetService;
    this.medicinePricesRepository = medicinePricesRepository;
    this.purchasesRepository = purchasesRepository;
  }

  public async getAlerts(householdId: string, userId: string, thresholdDays = 7) {
    const burnRates = await this.regimensService.calculateBurnRates(householdId, userId);

    const triggered = burnRates.filter(
      (br) => br.daysUntilEmpty !== null && br.daysUntilEmpty <= thresholdDays,
    );

    if (triggered.length === 0) return [];

    // Get strength data from cabinet aggregate
    const summaries = await this.cabinetRepository.getAggregateSummary(householdId);
    const summaryMap = new Map<
      string,
      { medicineStrength: number; medicineStrengthUnit: string }
    >();
    for (const s of summaries) {
      summaryMap.set(s._id as string, {
        medicineStrength: s.medicineStrength as number,
        medicineStrengthUnit: s.medicineStrengthUnit as string,
      });
    }

    // Get pending stock from ordered purchases
    const pendingStockRows = await this.purchasesRepository.getPendingMedicineStock(householdId);
    const pendingStockMap = new Map<string, number>();
    for (const row of pendingStockRows) {
      pendingStockMap.set(row.medicineId, row.totalUnits);
    }

    const alerts = await Promise.all(
      triggered.map(async (br) => {
        const summary = summaryMap.get(br.medicineId);
        const suggestedQuantity = Math.ceil(br.dailyConsumption * 30);
        const pendingOrderStock = pendingStockMap.get(br.medicineId) ?? 0;
        const daysUntilEmptyWithOrders =
          br.dailyConsumption > 0
            ? (br.totalInCabinet + pendingOrderStock) / br.dailyConsumption
            : null;

        const [latestRecord, comparisons] = await Promise.all([
          this.medicinePricesRepository.getLatestForMedicine(householdId, br.medicineId),
          this.medicinePricesRepository.compareStores(householdId, br.medicineId),
        ]);

        return {
          medicineId: br.medicineId,
          medicineName: br.medicineName,
          medicineStrength: summary?.medicineStrength ?? 0,
          medicineStrengthUnit: (summary?.medicineStrengthUnit ?? 'mg') as never,
          daysUntilEmpty: br.daysUntilEmpty as number,
          dailyConsumption: br.dailyConsumption,
          currentStock: br.totalInCabinet,
          pendingOrderStock,
          daysUntilEmptyWithOrders,
          suggestedQuantity,
          lastKnownPrice: latestRecord
            ? {
                price: latestRecord.price as number,
                pricePerUnit: latestRecord.pricePerUnit as number,
                storeName: latestRecord.storeName as string,
                storeId: latestRecord.storeId as string,
                date: latestRecord.date as Date,
              }
            : undefined,
          cheapestOption:
            comparisons.length > 0
              ? {
                  price: comparisons[0].latestPrice,
                  pricePerUnit: comparisons[0].latestPricePerUnit,
                  storeName: comparisons[0].storeName,
                  storeId: comparisons[0].storeId,
                  date: comparisons[0].date,
                }
              : undefined,
        };
      }),
    );

    return alerts;
  }

  public async createList(data: CreateRefillListInput, householdId: string, userId: string) {
    let items: Array<{
      medicineId: string;
      medicineName: string;
      quantity: number;
      unit: string;
      estimatedPrice?: number;
      storeId?: string;
      notes?: string;
    }> = [];

    if (data.fromAlerts) {
      const alerts = await this.getAlerts(householdId, userId, data.thresholdDays);
      items = alerts.map((alert) => ({
        medicineId: alert.medicineId,
        medicineName: alert.medicineName,
        quantity: alert.suggestedQuantity,
        unit: 'tablet' as string,
        estimatedPrice: alert.cheapestOption?.price ?? alert.lastKnownPrice?.price,
        storeId: alert.cheapestOption?.storeId ?? alert.lastKnownPrice?.storeId,
      }));
    } else if (data.items) {
      items = data.items.map((item) => ({
        medicineId: item.medicineId,
        medicineName: item.medicineName,
        quantity: item.quantity,
        unit: item.unit as string,
        estimatedPrice: item.estimatedPrice,
        storeId: item.storeId,
        notes: item.notes,
      }));
    }

    const totalEstimatedCost =
      items.length > 0
        ? items.reduce((sum, item) => sum + (item.estimatedPrice ?? 0), 0) || undefined
        : undefined;

    return this.refillsRepository.create({
      householdId,
      name: data.name,
      status: RefillListStatus.ACTIVE,
      preferredStoreId: data.preferredStoreId,
      totalEstimatedCost,
      createdBy: userId,
      items,
    });
  }

  public async list(householdId: string, query: RefillListQueryInput) {
    return this.refillsRepository.findByHousehold(householdId, query);
  }

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

  public async updateList(id: string, householdId: string, data: UpdateRefillListInput) {
    await this.getById(id, householdId);
    const updated = await this.refillsRepository.update(id, householdId, data);
    if (!updated) throw new NotFoundError('Refill list not found');
    return updated;
  }

  public async updateItem(
    listId: string,
    householdId: string,
    itemId: string,
    data: UpdateRefillListItemInput,
  ) {
    await this.getById(listId, householdId);

    const updateData: UpdateRefillListItemInput & { checkedAt?: Date } = { ...data };
    if (data.checked === true) {
      updateData.checkedAt = new Date();
    }

    const updated = await this.refillsRepository.updateItem(
      listId,
      householdId,
      itemId,
      updateData,
    );
    if (!updated) throw new NotFoundError('Refill list or item not found');
    return updated;
  }

  public async addToCabinet(listId: string, householdId: string, userId: string) {
    const list = await this.getById(listId, householdId);

    const checkedItems = (
      list.items as Array<{
        _id: { toString: () => string };
        medicineId: string;
        medicineName: string;
        quantity: number;
        unit: string;
        actualPrice?: number;
        storeId?: string;
        checked: boolean;
        addedToCabinet: boolean;
      }>
    ).filter((item) => item.checked && !item.addedToCabinet);

    if (checkedItems.length === 0) {
      return { addedCount: 0, priceRecordsCreated: 0 };
    }

    let addedCount = 0;
    const addedItemIds: string[] = [];

    for (const item of checkedItems) {
      await this.cabinetService.addItem(
        {
          medicineId: item.medicineId,
          quantity: item.quantity,
          unit: item.unit as never,
          unitPrice:
            item.actualPrice !== undefined && item.quantity > 0
              ? item.actualPrice / item.quantity
              : undefined,
          totalPrice: item.actualPrice,
          storeId: item.storeId,
          purchaseDate: new Date().toISOString(),
        },
        householdId,
        userId,
      );
      addedCount++;
      addedItemIds.push(item._id.toString());
    }

    await this.refillsRepository.markItemsAddedToCabinet(listId, householdId, addedItemIds);

    return { addedCount, priceRecordsCreated: 0 };
  }

  public async getStoreComparison(listId: string, householdId: string) {
    const list = await this.getById(listId, householdId);

    const medicineIds = [
      ...new Set((list.items as Array<{ medicineId: string }>).map((item) => item.medicineId)),
    ];

    const comparisons = await Promise.all(
      medicineIds.map(async (medicineId) => {
        const storeOptions = await this.medicinePricesRepository.compareStores(
          householdId,
          medicineId,
        );
        return { medicineId, storeOptions };
      }),
    );

    return comparisons.filter((c) => c.storeOptions.length > 0);
  }
}
