import type { ShoppingListsRepository } from './shopping-lists.repository.js';
import type { ShoppingGapService } from '../meal-plans/shopping-gap.service.js';
import type { PantryService } from '../pantry/pantry.service.js';
import type { ProductsRepository } from '../products/products.repository.js';
import type { PricesService } from '../prices/prices.service.js';
import type { MealPlanRepository } from '../meal-plans/meal-plans.repository.js';
import type {
  CreateShoppingListInput,
  UpdateShoppingListInput,
  AddShoppingItemInput,
  UpdateShoppingItemInput,
  ShoppingListStatus,
  ShoppingItem,
} from '@meshitrack/shared';
import { ShoppingListSourceType } from '@meshitrack/shared';
import { StorageLocation, type ServingUnit } from '@meshitrack/shared';
import { NotFoundError } from '../../common/errors.js';
import { v4 as uuidv4 } from 'uuid';

interface Deps {
  shoppingListsRepository: ShoppingListsRepository;
  shoppingGapService: ShoppingGapService;
  pantryService: PantryService;
  productsRepository: ProductsRepository;
  pricesService: PricesService;
  mealPlanRepository: MealPlanRepository;
}

export class ShoppingListsService {
  private readonly shoppingListsRepository: ShoppingListsRepository;
  private readonly shoppingGapService: ShoppingGapService;
  private readonly pantryService: PantryService;
  private readonly productsRepository: ProductsRepository;
  private readonly pricesService: PricesService;
  private readonly mealPlanRepository: MealPlanRepository;

  public constructor({
    shoppingListsRepository,
    shoppingGapService,
    pantryService,
    productsRepository,
    pricesService,
    mealPlanRepository,
  }: Deps) {
    this.shoppingListsRepository = shoppingListsRepository;
    this.shoppingGapService = shoppingGapService;
    this.pantryService = pantryService;
    this.productsRepository = productsRepository;
    this.pricesService = pricesService;
    this.mealPlanRepository = mealPlanRepository;
  }

  public async list(householdId: string) {
    return this.shoppingListsRepository.list(householdId);
  }

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

  public async create(data: CreateShoppingListInput, householdId: string, userId: string) {
    // Optionally calculate estimates for pre-populated items
    const hydratedItems: ShoppingItem[] = [];
    let runningTotal = 0;

    for (const it of data.items || []) {
      const itemId = uuidv4();
      let estimatedPrice: number | undefined;
      let category: string | undefined = it.category;

      if (it.productId) {
        const [p, est] = await Promise.all([
          this.productsRepository.findById(it.productId, householdId),
          this.pricesService.estimatePrice(it.productId, householdId, data.preferredStoreId),
        ]);
        if (p) category = category || p.category;
        if (est) {
          estimatedPrice = est;
          runningTotal += est;
        }
      }

      hydratedItems.push({
        id: itemId,
        productId: it.productId,
        customName: it.customName,
        quantity: it.quantity,
        unit: it.unit,
        checked: false,
        addedToPantry: false,
        notes: it.notes,
        estimatedPrice,
        category: category as any,
      });
    }

    return this.shoppingListsRepository.create({
      ...data,
      items: hydratedItems,
      householdId,
      createdBy: userId,
      status: 'active',
      totalEstimatedCost: runningTotal > 0 ? Math.round(runningTotal * 100) / 100 : undefined,
    });
  }

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

  public async delete(id: string, householdId: string) {
    await this.getById(id, householdId);
    return this.shoppingListsRepository.delete(id, householdId);
  }

  // --- Live Item Actions ---

  public async addItem(id: string, householdId: string, data: AddShoppingItemInput) {
    await this.getById(id, householdId);

    let estimatedPrice: number | undefined;
    let category: string | undefined = data.category;

    if (data.productId) {
      const [p, est] = await Promise.all([
        this.productsRepository.findById(data.productId, householdId),
        this.pricesService.estimatePrice(data.productId, householdId),
      ]);
      if (p) category = category || p.category;
      if (est) estimatedPrice = est;
    }

    const newItem: ShoppingItem = {
      id: uuidv4(),
      productId: data.productId,
      customName: data.customName,
      quantity: data.quantity,
      unit: data.unit,
      checked: false,
      addedToPantry: false,
      notes: data.notes,
      estimatedPrice,
      category: category as any,
    };

    const updated = await this.shoppingListsRepository.addItem(id, householdId, newItem);
    if (!updated) throw new NotFoundError('Shopping list not found');
    return { list: updated, addedItem: newItem };
  }

  public async updateItem(
    id: string,
    householdId: string,
    itemId: string,
    data: UpdateShoppingItemInput,
    userId: string,
  ) {
    const updates: Partial<ShoppingItem> = { ...data };

    if (data.checked !== undefined) {
      updates.checkedAt = data.checked ? new Date() : undefined;
      updates.checkedBy = data.checked ? userId : undefined;
    }

    const updated = await this.shoppingListsRepository.updateItem(id, householdId, itemId, updates);
    if (!updated) throw new NotFoundError('Shopping item or list not found');
    return updated;
  }

  public async removeItem(id: string, householdId: string, itemId: string) {
    const updated = await this.shoppingListsRepository.removeItem(id, householdId, itemId);
    if (!updated) throw new NotFoundError('Shopping list not found');
    return updated;
  }

  // --- Workflow Methods ---

  /**
   * Analyzes missing ingredients for a meal plan and pre-builds a targeted list.
   */
  public async createFromMealPlan(mealPlanId: string, householdId: string, userId: string) {
    const plan = await this.mealPlanRepository.findById(mealPlanId, householdId);
    if (!plan) throw new NotFoundError('Meal plan not found');

    const report = await this.shoppingGapService.calculateGap(householdId, mealPlanId);

    const listItems: ShoppingItem[] = [];
    let runningTotal = 0;

    for (const gap of report.missingItems) {
      const estPrice = await this.pricesService.estimatePrice(gap.productId, householdId);
      if (estPrice) runningTotal += estPrice;

      listItems.push({
        id: uuidv4(),
        productId: gap.productId,
        quantity: gap.missingQuantity,
        unit: gap.unit as ServingUnit,
        checked: false,
        addedToPantry: false,
        category: gap.category as any,
        estimatedPrice: estPrice || undefined,
      });
    }

    const dateStr = new Date((plan as any).weekStartDate).toLocaleDateString('en-US', {
      month: 'short',
      day: 'numeric',
    });
    const name = `Groceries for Week of ${dateStr}`;

    const newList = await this.shoppingListsRepository.create({
      name,
      items: listItems,
      householdId,
      createdBy: userId,
      status: 'active',
      createdFrom: {
        type: ShoppingListSourceType.MEAL_PLAN,
        referenceId: mealPlanId,
      },
      mealPlanId,
      totalEstimatedCost: runningTotal > 0 ? Math.round(runningTotal * 100) / 100 : undefined,
    });

    // Re-link backing meal plan to its generated list for simplified visual tracking
    await this.mealPlanRepository.update(mealPlanId, householdId, {
      shoppingListId: newList._id.toString(),
    });

    return newList;
  }

  /**
   * Migrates all checked grocery items into active pantry items and records final purchase prices.
   */
  public async syncCheckedToPantry(id: string, householdId: string, userId: string) {
    const list = await this.getById(id, householdId);

    let addedCount = 0;
    let pricesLogged = 0;

    const pendingItems = list.items.filter(
      (it: ShoppingItem) => it.checked && !it.addedToPantry && it.productId,
    );

    for (const item of pendingItems) {
      // 1. Promote item to active pantry
      await this.pantryService.create(
        {
          productId: item.productId,
          storageLocation: StorageLocation.PANTRY, // Generic fallback location
          quantity: item.quantity,
          unit: item.unit as ServingUnit,
          purchasePrice: item.actualPrice || undefined,
          storeId: (item.storeId || list.preferredStoreId || undefined) as string | undefined,
          notes: item.notes || undefined,
        },
        householdId,
        userId,
      );
      addedCount++;

      // 2. Log final point-in-time pricing ledger entry if user input final price
      if (item.actualPrice != null && item.actualPrice > 0) {
        const storeId = item.storeId || list.preferredStoreId;
        if (storeId) {
          await this.pricesService.recordPrice(
            {
              productId: item.productId,
              storeId: storeId as string,
              price: item.actualPrice as number,
              quantity: item.quantity,
              unit: item.unit as ServingUnit,
              currency: 'USD',
            },
            householdId,
            userId,
          );
          pricesLogged++;
        }
      }

      // 3. Set item flag avoiding future sync duplications
      await this.shoppingListsRepository.updateItem(id, householdId, item.id, {
        addedToPantry: true,
      });
    }

    return { addedCount, pricesLogged };
  }

  /**
   * Analyzes price history to identify the cheapest store option for this basket.
   */
  public async getStoreComparison(id: string, householdId: string) {
    const list = await this.getById(id, householdId);
    const validItems = list.items.filter((it: ShoppingItem) => it.productId);

    // 1. Collate all recent pricing permutations for all products in this basket
    const storePricesMap = new Map<string, Map<string, number>>(); // storeId -> Map<productId, latestPrice>
    const storeNamesMap = new Map<string, string>();
    const allProductIds = validItems.map((it: ShoppingItem) => it.productId!);

    for (const productId of allProductIds) {
      const options = await this.pricesService.compareStores(productId, householdId);
      for (const opt of options) {
        storeNamesMap.set(opt.storeId, opt.storeName);
        if (!storePricesMap.has(opt.storeId)) {
          storePricesMap.set(opt.storeId, new Map());
        }
        storePricesMap.get(opt.storeId)!.set(productId, opt.latestPrice);
      }
    }

    // 2. Compile comparative totals per store option
    const singleStoreOptions: Array<{
      storeId: string;
      storeName: string;
      estimatedTotal: number;
      itemsCovered: number;
      itemsMissing: string[];
    }> = [];

    for (const [storeId, priceMap] of storePricesMap.entries()) {
      let sum = 0;
      let count = 0;
      const missing: string[] = [];

      for (const item of validItems) {
        const p = priceMap.get(item.productId!);
        if (p !== undefined) {
          sum += p; // Simple unit scaling could be factored in, using simple latest price sum here
          count++;
        } else {
          missing.push(item.productId!);
        }
      }

      singleStoreOptions.push({
        storeId,
        storeName: storeNamesMap.get(storeId) || 'Store',
        estimatedTotal: Math.round(sum * 100) / 100,
        itemsCovered: count,
        itemsMissing: missing,
      });
    }

    // Sort to surface the cheapest/fullest single store options first
    singleStoreOptions.sort(
      (a, b) => b.itemsCovered - a.itemsCovered || a.estimatedTotal - b.estimatedTotal,
    );

    return { singleStoreOptions };
  }
}
