import type { PricesRepository } from './prices.repository.js';
import type { ProductsRepository } from '../products/products.repository.js';
import type { StoresRepository } from '../stores/stores.repository.js';
import type {
  CreatePriceRecordInput,
  BulkPriceRecordInput,
  PriceHistoryQueryInput,
} from '@meshitrack/shared';
import { NotFoundError } from '../../common/errors.js';

interface Deps {
  pricesRepository: PricesRepository;
  productsRepository: ProductsRepository;
  storesRepository: StoresRepository;
}

export class PricesService {
  private readonly pricesRepository: PricesRepository;
  private readonly productsRepository: ProductsRepository;
  private readonly storesRepository: StoresRepository;

  public constructor({ pricesRepository, productsRepository, storesRepository }: Deps) {
    this.pricesRepository = pricesRepository;
    this.productsRepository = productsRepository;
    this.storesRepository = storesRepository;
  }

  /**
   * Validates entity existence, computes pricePerUnit, and persists record
   */
  public async recordPrice(data: CreatePriceRecordInput, householdId: string, userId: string) {
    const [product, store] = await Promise.all([
      this.productsRepository.findById(data.productId, householdId),
      this.storesRepository.findById(data.storeId, householdId),
    ]);

    if (!product) throw new NotFoundError(`Product not found: ${data.productId}`);
    if (!store) throw new NotFoundError(`Store not found: ${data.storeId}`);

    const pricePerUnit = data.quantity > 0 ? data.price / data.quantity : data.price;

    return this.pricesRepository.create({
      householdId,
      productId: data.productId,
      productName: product.name as string,
      storeId: data.storeId,
      storeName: store.name as string,
      price: data.price,
      currency: data.currency,
      quantity: data.quantity,
      unit: data.unit,
      pricePerUnit,
      date: data.date ? new Date(data.date) : new Date(),
      receiptImageUrl: data.receiptImageUrl,
      notes: data.notes,
      createdBy: userId,
    });
  }

  /**
   * Ingests a list of purchased products in a single transaction
   */
  public async recordBulkPrices(data: BulkPriceRecordInput, householdId: string, userId: string) {
    const store = await this.storesRepository.findById(data.storeId, householdId);
    if (!store) throw new NotFoundError(`Store not found: ${data.storeId}`);

    const recordDate = data.date ? new Date(data.date) : new Date();
    const productIds = data.items.map((it) => it.productId);

    const products = await this.productsRepository.findByIds(householdId, productIds);
    const productMap = new Map(products.map((p) => [p._id.toString(), p]));

    const creationPayloads = data.items.map((item) => {
      const product = productMap.get(item.productId);
      if (!product) {
        throw new NotFoundError(`Product not found in catalog: ${item.productId}`);
      }

      const pricePerUnit = item.quantity > 0 ? item.price / item.quantity : item.price;

      return {
        householdId,
        productId: item.productId,
        productName: product.name as string,
        storeId: data.storeId,
        storeName: store.name as string,
        price: item.price,
        currency: 'USD', // Base fallback or pulled from household settings in future
        quantity: item.quantity,
        unit: item.unit,
        pricePerUnit,
        date: recordDate,
        notes: item.notes,
        createdBy: userId,
      };
    });

    return this.pricesRepository.createMany(creationPayloads);
  }

  public async getPriceHistory(
    productId: string,
    householdId: string,
    query: PriceHistoryQueryInput,
  ) {
    return this.pricesRepository.findByProduct(householdId, productId, query);
  }

  public async compareStores(productId: string, householdId: string) {
    return this.pricesRepository.compareStores(householdId, productId);
  }

  public async getAnalytics(householdId: string) {
    return this.pricesRepository.getAnalytics(householdId);
  }

  /**
   * Retrieves the most recent price recorded for this item (optionally restricted to a store)
   * to enable quick population of estimated basket subtotals on fresh lists.
   */
  public async estimatePrice(
    productId: string,
    householdId: string,
    storeId?: string,
  ): Promise<number | null> {
    const latest = await this.pricesRepository.getLatestForProduct(householdId, productId, storeId);
    if (!latest) {
      // If a specific store was requested but has no history, fall back to the generic latest across all stores
      if (storeId) {
        const genericLatest = await this.pricesRepository.getLatestForProduct(
          householdId,
          productId,
        );
        return genericLatest ? genericLatest.price : null;
      }
      return null;
    }
    return latest.price;
  }
}
