import type { PurchasesRepository } from './purchases.repository.js';
import type { StoresRepository } from '../stores/stores.repository.js';
import type { MedicineProductsRepository } from '../medicine-products/medicine-products.repository.js';
import type { MedicinePricesRepository } from '../medicine-prices/medicine-prices.repository.js';
import type { CabinetService } from '../cabinet/cabinet.service.js';
import type {
  CreatePurchaseInput,
  UpdatePurchaseInput,
  PurchaseQueryInput,
} from '@meshitrack/shared';
import { type DosageUnit } from '@meshitrack/shared';
import { NotFoundError, BadRequestError } from '../../common/errors.js';

interface Deps {
  purchasesRepository: PurchasesRepository;
  cabinetService: CabinetService;
  storesRepository: StoresRepository;
  medicineProductsRepository: MedicineProductsRepository;
  medicinePricesRepository: MedicinePricesRepository;
}

export class PurchasesService {
  private readonly purchasesRepository: PurchasesRepository;
  private readonly cabinetService: CabinetService;
  private readonly storesRepository: StoresRepository;
  private readonly medicineProductsRepository: MedicineProductsRepository;
  private readonly medicinePricesRepository: MedicinePricesRepository;

  public constructor({
    purchasesRepository,
    cabinetService,
    storesRepository,
    medicineProductsRepository,
    medicinePricesRepository,
  }: Deps) {
    this.purchasesRepository = purchasesRepository;
    this.cabinetService = cabinetService;
    this.storesRepository = storesRepository;
    this.medicineProductsRepository = medicineProductsRepository;
    this.medicinePricesRepository = medicinePricesRepository;
  }

  public async create(data: CreatePurchaseInput, householdId: string, userId: string) {
    const store = await this.storesRepository.findById(data.storeId, householdId);
    if (!store) throw new NotFoundError('Store not found');

    const purchasedAt = data.purchasedAt ? new Date(data.purchasedAt) : new Date();

    const items: Array<{
      medicineProductId?: string;
      medicineId?: string;
      name: string;
      quantity: number;
      unit: string;
      actualPrice?: number;
      currency?: string;
      priceRecordId?: string;
      addedToCabinet: boolean;
    }> = [];

    for (const item of data.items) {
      let resolvedName = item.name;
      let resolvedMedicineId: string | undefined;

      if (item.medicineProductId) {
        const product = await this.medicineProductsRepository.findById(
          item.medicineProductId,
          householdId,
        );
        if (!product)
          throw new NotFoundError(`Medicine product not found: ${item.medicineProductId}`);
        if (!resolvedName || resolvedName === item.name) {
          resolvedName = product.brand ?? resolvedName;
        }
        resolvedMedicineId = product.medicineId as string;
      }

      items.push({
        medicineProductId: item.medicineProductId,
        medicineId: resolvedMedicineId,
        name: resolvedName,
        quantity: item.quantity,
        unit: item.unit,
        actualPrice: item.actualPrice,
        currency: item.currency,
        addedToCabinet: false,
      });
    }

    if (data.status === 'in_cabinet') {
      for (let i = 0; i < items.length; i++) {
        const item = items[i];
        if (item.medicineProductId && item.medicineId) {
          await this.cabinetService.addItem(
            {
              medicineId: item.medicineId,
              medicineProductId: item.medicineProductId,
              quantity: item.quantity,
              unit: item.unit as DosageUnit,
              unitPrice:
                item.actualPrice !== undefined && item.quantity > 0
                  ? item.actualPrice / item.quantity
                  : undefined,
              totalPrice: item.actualPrice,
              storeId: data.storeId,
              purchaseDate: purchasedAt.toISOString(),
            },
            householdId,
            userId,
          );

          if (item.actualPrice !== undefined) {
            const product = await this.medicineProductsRepository.findById(
              item.medicineProductId,
              householdId,
            );
            if (product) {
              await this.medicinePricesRepository.create({
                householdId,
                medicineProductId: item.medicineProductId,
                medicineProductBrand: (product.brand as string) ?? (product.medicineName as string),
                medicineId: item.medicineId,
                /* v8 ignore next */
                medicineName: (product.medicineName as string) ?? '',
                storeId: data.storeId,
                storeName: store.name as string,
                price: item.actualPrice,
                currency: item.currency ?? 'USD',
                quantity: item.quantity,
                unit: item.unit,
                /* v8 ignore next */
                pricePerUnit:
                  item.quantity > 0 ? item.actualPrice / item.quantity : item.actualPrice,
                date: purchasedAt,
                isInsurancePrice: false,
                createdBy: userId,
              });
            }
          }

          items[i] = { ...item, addedToCabinet: true };
        }
      }
    }

    return this.purchasesRepository.create({
      householdId,
      storeId: data.storeId,
      storeName: store.name as string,
      status: data.status,
      items,
      notes: data.notes,
      purchasedAt,
      createdBy: userId,
    });
  }

  public async receive(id: string, householdId: string, userId: string) {
    const purchase = await this.purchasesRepository.findById(id, householdId);
    if (!purchase) throw new NotFoundError('Purchase not found');
    if (purchase.status !== 'ordered') {
      throw new BadRequestError('Purchase is not in ordered status');
    }

    let addedCount = 0;
    let priceRecordsCreated = 0;

    const itemsToUpdate: number[] = [];

    const items = purchase.items as Array<{
      medicineProductId?: string;
      medicineId?: string;
      name: string;
      quantity: number;
      unit: string;
      actualPrice?: number;
      currency?: string;
      addedToCabinet: boolean;
    }>;

    for (let i = 0; i < items.length; i++) {
      const item = items[i];
      if (item.medicineProductId && item.medicineId && !item.addedToCabinet) {
        await this.cabinetService.addItem(
          {
            medicineId: item.medicineId,
            medicineProductId: item.medicineProductId,
            quantity: item.quantity,
            unit: item.unit as DosageUnit,
            unitPrice:
              item.actualPrice !== undefined && item.quantity > 0
                ? item.actualPrice / item.quantity
                : undefined,
            totalPrice: item.actualPrice,
            storeId: purchase.storeId as string,
            purchaseDate: (purchase.purchasedAt as Date).toISOString(),
          },
          householdId,
          userId,
        );
        addedCount++;

        if (item.actualPrice !== undefined) {
          const product = await this.medicineProductsRepository.findById(
            item.medicineProductId,
            householdId,
          );
          if (product) {
            await this.medicinePricesRepository.create({
              householdId,
              medicineProductId: item.medicineProductId,
              medicineProductBrand: (product.brand as string) ?? (product.medicineName as string),
              medicineId: item.medicineId,
              /* v8 ignore next */
              medicineName: (product.medicineName as string) ?? '',
              storeId: purchase.storeId as string,
              storeName: purchase.storeName as string,
              price: item.actualPrice,
              currency: item.currency ?? 'USD',
              quantity: item.quantity,
              unit: item.unit,
              /* v8 ignore next */
              pricePerUnit: item.quantity > 0 ? item.actualPrice / item.quantity : item.actualPrice,
              date: purchase.purchasedAt as Date,
              isInsurancePrice: false,
              createdBy: userId,
            });
            priceRecordsCreated++;
          }
        }

        itemsToUpdate.push(i);
      }
    }

    await this.purchasesRepository.receiveAll(id, householdId);

    return { addedCount, priceRecordsCreated };
  }

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

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

  public async update(id: string, householdId: string, data: UpdatePurchaseInput) {
    await this.getById(id, householdId);
    const updated = await this.purchasesRepository.update(id, householdId, data);
    if (!updated) throw new NotFoundError('Purchase not found');
    return updated;
  }

  public async delete(id: string, householdId: string) {
    const deleted = await this.purchasesRepository.softDelete(id, householdId);
    if (!deleted) throw new NotFoundError('Purchase not found or cannot be deleted');
    return deleted;
  }

  public async getPendingStockByMedicine(householdId: string): Promise<Map<string, number>> {
    const results = await this.purchasesRepository.getPendingMedicineStock(householdId);
    const map = new Map<string, number>();
    for (const r of results) {
      map.set(r.medicineId, r.totalUnits);
    }
    return map;
  }
}
