import type { PantryRepository } from './pantry.repository.js';
import type { FreshnessRulesRepository } from '../freshness-rules/freshness-rules.repository.js';
import type { ProductsRepository } from '../products/products.repository.js';
import { FreshnessCalculatorService } from './freshness-calculator.service.js';
import { ItemStatus } from '@meshitrack/shared';
import type {
  CreatePantryItemInput,
  UpdatePantryItemInput,
  TransitionPantryItemInput,
  BatchTransitionInput,
  PantryQueryInput,
  ExpiringQueryInput,
  WasteStatsQueryInput,
} from '@meshitrack/shared';
import { NotFoundError, BadRequestError } from '../../common/errors.js';

const VALID_TRANSITIONS: Record<string, string[]> = {
  [ItemStatus.SEALED]: [ItemStatus.OPENED, ItemStatus.CONSUMED, ItemStatus.DISCARDED],
  [ItemStatus.OPENED]: [ItemStatus.PREPARED, ItemStatus.CONSUMED, ItemStatus.DISCARDED],
  [ItemStatus.PREPARED]: [ItemStatus.CONSUMED, ItemStatus.DISCARDED],
};

interface Deps {
  pantryRepository: PantryRepository;
  freshnessRulesRepository: FreshnessRulesRepository;
  productsRepository: ProductsRepository;
}

export class PantryService {
  private readonly pantryRepository: PantryRepository;
  private readonly freshnessRulesRepository: FreshnessRulesRepository;
  private readonly productsRepository: ProductsRepository;
  private readonly freshnessCalculator = new FreshnessCalculatorService();

  public constructor({ pantryRepository, freshnessRulesRepository, productsRepository }: Deps) {
    this.pantryRepository = pantryRepository;
    this.freshnessRulesRepository = freshnessRulesRepository;
    this.productsRepository = productsRepository;
  }

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

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

  public async create(data: CreatePantryItemInput, householdId: string, createdBy: string) {
    const product = await this.productsRepository.findById(data.productId, householdId);
    if (!product) throw new NotFoundError('Product not found');

    const purchaseDate = data.purchaseDate ? new Date(data.purchaseDate) : new Date();
    const expirationDate = data.expirationDate ? new Date(data.expirationDate) : undefined;

    const rule = await this.freshnessRulesRepository.findApplicableRule(
      householdId,
      (product as Record<string, unknown>).category as string,
      data.storageLocation,
    );

    const freshnessEstimate = this.freshnessCalculator.calculate(
      {
        status: ItemStatus.SEALED,
        storageLocation: data.storageLocation,
        purchaseDate,
        expirationDate,
      },
      rule,
    );

    return this.pantryRepository.create({
      householdId,
      productId: data.productId,
      productName: (product as Record<string, unknown>).name as string,
      storageLocation: data.storageLocation,
      quantity: data.quantity,
      unit: data.unit,
      purchaseDate,
      ...(expirationDate ? { expirationDate } : {}),
      status: ItemStatus.SEALED,
      freshnessEstimate,
      ...(data.notes ? { notes: data.notes } : {}),
      ...(data.purchasePrice != null ? { purchasePrice: data.purchasePrice } : {}),
      ...(data.storeId ? { storeId: data.storeId } : {}),
      createdBy,
    });
  }

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

  public async transition(id: string, householdId: string, data: TransitionPantryItemInput) {
    const item = await this.getById(id, householdId);
    const currentStatus = (item as Record<string, unknown>).status as string;
    const allowed = VALID_TRANSITIONS[currentStatus];

    if (!allowed || !allowed.includes(data.status)) {
      throw new BadRequestError(`Cannot transition from '${currentStatus}' to '${data.status}'`);
    }

    const now = data.date ? new Date(data.date) : new Date();
    const updates: Record<string, unknown> = { status: data.status };

    if (data.status === ItemStatus.OPENED) {
      updates['openedDate'] = now;
    } else if (data.status === ItemStatus.PREPARED) {
      updates['preparedDate'] = now;
    }

    if (data.notes) updates['notes'] = data.notes;

    // Recalculate freshness if transitioning to opened (shelf life changes)
    if (data.status === ItemStatus.OPENED) {
      const product = await this.productsRepository.findById(
        (item as Record<string, unknown>).productId as string,
        householdId,
      );
      const category = product
        ? ((product as Record<string, unknown>).category as string)
        : 'other';
      const storageLocation = (item as Record<string, unknown>).storageLocation as string;

      const rule = await this.freshnessRulesRepository.findApplicableRule(
        householdId,
        category,
        storageLocation,
      );

      const freshness = this.freshnessCalculator.calculate(
        {
          status: ItemStatus.OPENED,
          storageLocation: storageLocation as never,
          purchaseDate: new Date((item as Record<string, unknown>).purchaseDate as string),
          expirationDate: (item as Record<string, unknown>).expirationDate as Date | undefined,
          openedDate: now,
        },
        rule,
      );

      updates['freshnessEstimate'] = freshness;
    }

    const updated = await this.pantryRepository.update(id, householdId, updates);
    if (!updated) throw new NotFoundError('Pantry item not found');
    return updated;
  }

  public async batchTransition(householdId: string, data: BatchTransitionInput) {
    const items = await this.pantryRepository.findByIds(data.itemIds, householdId);

    const validIds: string[] = [];
    for (const item of items) {
      const currentStatus = (item as Record<string, unknown>).status as string;
      const allowed = VALID_TRANSITIONS[currentStatus];
      if (allowed && allowed.includes(data.status)) {
        validIds.push((item as Record<string, unknown>)._id!.toString());
      }
    }

    const extra: Record<string, unknown> = {};
    if (data.date) extra['updatedAt'] = new Date(data.date);
    if (data.notes) extra['notes'] = data.notes;

    const transitioned =
      validIds.length > 0
        ? await this.pantryRepository.bulkUpdateStatus(
            validIds,
            householdId,
            data.status as ItemStatus,
            extra,
          )
        : 0;

    return {
      transitioned,
      failed: data.itemIds.length - transitioned,
    };
  }

  public async getExpiringSoon(householdId: string, query: ExpiringQueryInput) {
    return this.pantryRepository.findExpiringSoon(
      householdId,
      query.days,
      query.cursor,
      query.limit,
    );
  }

  public async getWasteStats(householdId: string, query: WasteStatsQueryInput) {
    const { start, end } = this.getPeriodDates(query.period);

    const [stats] = (await this.pantryRepository.getWasteStats(householdId, start, end)) as Record<
      string,
      number
    >[];
    const totalConsumed = stats?.totalConsumed ?? 0;
    const totalDiscarded = stats?.totalDiscarded ?? 0;
    const total = totalConsumed + totalDiscarded;
    const wastePercentage = total > 0 ? Math.round((totalDiscarded / total) * 10000) / 100 : 0;

    const topWastedProducts = await this.pantryRepository.getTopWastedProducts(
      householdId,
      start,
      end,
    );

    return {
      period: { start: start.toISOString(), end: end.toISOString() },
      totalItemsConsumed: totalConsumed,
      totalItemsDiscarded: totalDiscarded,
      wastePercentage,
      topWastedCategories: [],
      topWastedProducts: topWastedProducts as {
        productId: string;
        productName: string;
        count: number;
      }[],
    };
  }

  public async refreshAllFreshness(householdId: string) {
    const items = await this.pantryRepository.findActiveByHousehold(householdId);

    for (const item of items) {
      const rec = item as Record<string, unknown>;
      const product = await this.productsRepository.findById(rec.productId as string, householdId);
      const category = product
        ? ((product as Record<string, unknown>).category as string)
        : 'other';

      const rule = await this.freshnessRulesRepository.findApplicableRule(
        householdId,
        category,
        rec.storageLocation as string,
      );

      const freshness = this.freshnessCalculator.calculate(
        {
          status: rec.status as ItemStatus,
          storageLocation: rec.storageLocation as never,
          purchaseDate: new Date(rec.purchaseDate as string),
          expirationDate: rec.expirationDate as Date | undefined,
          openedDate: rec.openedDate as Date | undefined,
        },
        rule,
      );

      const newStatus =
        freshness.urgency === 'expired' && this.freshnessCalculator.isActive(rec.status as string)
          ? ItemStatus.EXPIRED
          : undefined;

      await this.pantryRepository.updateFreshness(
        (rec._id as { toString(): string }).toString(),
        freshness as unknown as Record<string, unknown>,
        newStatus,
      );
    }
  }

  public async delete(id: string, householdId: string) {
    const item = await this.getById(id, householdId);
    await this.pantryRepository.delete(id, householdId);
    return item;
  }

  private getPeriodDates(period: string): { start: Date; end: Date } {
    const end = new Date();
    const start = new Date();

    switch (period) {
      case 'week':
        start.setDate(end.getDate() - 7);
        break;
      case 'month':
        start.setMonth(end.getMonth() - 1);
        break;
      case 'quarter':
        start.setMonth(end.getMonth() - 3);
        break;
      case 'year':
        start.setFullYear(end.getFullYear() - 1);
        break;
    }

    return { start, end };
  }
}
