import type { CabinetRepository } from './cabinet.repository.js';
import type { MedicinesRepository } from '../medicines/medicines.repository.js';
import type { MedicineProductsRepository } from '../medicine-products/medicine-products.repository.js';
import type { CabinetEventsService } from '../cabinet-events/cabinet-events.service.js';
import { CabinetEventType, CabinetEventSourceType } from '@meshitrack/shared';
import type {
  CreateCabinetItemInput,
  UpdateCabinetItemInput,
  CabinetQueryInput,
} from '@meshitrack/shared';
import { NotFoundError, BadRequestError } from '../../common/errors.js';

interface Deps {
  cabinetRepository: CabinetRepository;
  medicinesRepository: MedicinesRepository;
  medicineProductsRepository: MedicineProductsRepository;
  cabinetEventsService: CabinetEventsService;
}

export class CabinetService {
  private readonly cabinetRepository: CabinetRepository;
  private readonly medicinesRepository: MedicinesRepository;
  private readonly medicineProductsRepository: MedicineProductsRepository;
  private readonly cabinetEventsService: CabinetEventsService;

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

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

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

  public async getSummary(householdId: string) {
    const results = await this.cabinetRepository.getAggregateSummary(householdId);
    return results.map((r: Record<string, unknown>) => ({
      medicineId: r._id as string,
      medicineName: r.medicineName as string,
      medicineStrength: r.medicineStrength as number,
      medicineStrengthUnit: r.medicineStrengthUnit as string,
      medicineForm: r.medicineForm as string,
      totalQuantity: r.totalQuantity as number,
      unit: r.unit as string,
      earliestExpiry: r.earliestExpiry ? (r.earliestExpiry as Date).toISOString() : null,
      itemCount: r.itemCount as number,
    }));
  }

  public async addItem(data: CreateCabinetItemInput, householdId: string, createdBy: string) {
    const medicine = await this.medicinesRepository.findById(data.medicineId, householdId);
    if (!medicine) {
      throw new NotFoundError('Medicine not found');
    }

    let productBrand: string | undefined;
    let concentration: number | undefined;
    let concentrationUnit: string | undefined;
    if (data.medicineProductId) {
      const product = await this.medicineProductsRepository.findById(
        data.medicineProductId,
        householdId,
      );
      if (!product) {
        throw new NotFoundError('Medicine product not found');
      }
      productBrand = product.brand;
      concentration = product.concentration ?? undefined;
      concentrationUnit = product.concentrationUnit ?? undefined;
    }

    const item = await this.cabinetRepository.create(
      {
        ...data,
        medicineName: medicine.name,
        medicineStrength: medicine.strength,
        medicineStrengthUnit: medicine.strengthUnit,
        medicineForm: medicine.form,
        medicineProductBrand: productBrand,
        concentration,
        concentrationUnit,
      },
      householdId,
      createdBy,
    );

    await this.cabinetEventsService.logEvent({
      householdId,
      userId: createdBy,
      cabinetItemId: item._id.toString(),
      medicineId: data.medicineId,
      medicineName: medicine.name,
      eventType: CabinetEventType.PURCHASED,
      quantity: data.quantity,
      quantityBefore: 0,
      quantityAfter: data.quantity,
      unitPrice: data.unitPrice,
      totalPrice: data.totalPrice,
      currency: data.currency,
      storeId: data.storeId,
      storeName: data.storeName,
      sourceType: CabinetEventSourceType.MANUAL,
    });

    return item;
  }

  public async update(
    id: string,
    householdId: string,
    data: UpdateCabinetItemInput,
    userId: string,
  ) {
    const existing = await this.getById(id, householdId);
    const updated = await this.cabinetRepository.update(id, householdId, data);
    if (!updated) throw new NotFoundError('Cabinet item not found');

    if (data.quantity !== undefined && data.quantity !== existing.quantity) {
      await this.cabinetEventsService.logEvent({
        householdId,
        userId,
        cabinetItemId: id,
        medicineId: existing.medicineId,
        medicineName: existing.medicineName,
        eventType: CabinetEventType.ADJUSTED,
        quantity: data.quantity - existing.quantity,
        quantityBefore: existing.quantity,
        quantityAfter: data.quantity,
        sourceType: CabinetEventSourceType.MANUAL,
      });
    }

    return updated;
  }

  public async adjustQuantity(
    id: string,
    householdId: string,
    delta: number,
    userId: string,
    reason?: string,
  ) {
    if (delta === 0) {
      throw new BadRequestError('Delta must be non-zero');
    }

    const existing = await this.getById(id, householdId);
    const updated = await this.cabinetRepository.adjustQuantity(id, householdId, delta);
    if (!updated) throw new NotFoundError('Cabinet item not found');

    await this.cabinetEventsService.logEvent({
      householdId,
      userId,
      cabinetItemId: id,
      medicineId: existing.medicineId,
      medicineName: existing.medicineName,
      eventType: CabinetEventType.ADJUSTED,
      quantity: delta,
      quantityBefore: existing.quantity,
      quantityAfter: updated.quantity,
      reason,
      sourceType: CabinetEventSourceType.MANUAL,
    });

    return updated;
  }

  public async getExpiringSoon(householdId: string, withinDays: number) {
    return this.cabinetRepository.findExpiringSoon(householdId, withinDays);
  }

  public async delete(id: string, householdId: string, userId: string) {
    const existing = await this.getById(id, householdId);
    const deleted = await this.cabinetRepository.softDelete(id, householdId);
    if (!deleted) throw new NotFoundError('Cabinet item not found');

    await this.cabinetEventsService.logEvent({
      householdId,
      userId,
      cabinetItemId: id,
      medicineId: existing.medicineId,
      medicineName: existing.medicineName,
      eventType: CabinetEventType.DELETED,
      quantity: -existing.quantity,
      quantityBefore: existing.quantity,
      quantityAfter: 0,
      sourceType: CabinetEventSourceType.MANUAL,
    });

    return deleted;
  }

  public async discard(
    id: string,
    householdId: string,
    userId: string,
    reason: string,
    notes?: string,
  ) {
    const existing = await this.getById(id, householdId);
    if (existing.quantity === 0) {
      throw new BadRequestError('Cannot discard an item with zero quantity');
    }

    const discarded = await this.cabinetRepository.discard(id, householdId);
    if (!discarded) throw new NotFoundError('Cabinet item not found');

    await this.cabinetEventsService.logEvent({
      householdId,
      userId,
      cabinetItemId: id,
      medicineId: existing.medicineId,
      medicineName: existing.medicineName,
      eventType: CabinetEventType.DISCARDED,
      quantity: -existing.quantity,
      quantityBefore: existing.quantity,
      quantityAfter: 0,
      reason,
      notes,
      sourceType: CabinetEventSourceType.MANUAL,
    });

    return discarded;
  }
}
