import { request } from 'undici';
import type { ProductsRepository } from './products.repository.js';
import { ProductCategory, ServingUnit, ProductSource } from '@meshitrack/shared';

interface Deps {
  productsRepository: ProductsRepository;
}

interface BarcodeLookupSuccess {
  found: true;
  product: Record<string, unknown>;
  cached: boolean;
}

interface BarcodeLookupNotFound {
  found: false;
}

export type BarcodeLookupResult = BarcodeLookupSuccess | BarcodeLookupNotFound;

const OFF_BASE_URL = 'https://world.openfoodfacts.org/api/v2/product';
const TIMEOUT_MS = 5000;
const USER_AGENT = 'MeshiTrack/0.1.0 (+self-hosted)';

const CATEGORY_MAP: Record<string, ProductCategory> = {
  'en:dairies': ProductCategory.DAIRY,
  'en:meats': ProductCategory.MEAT,
  'en:poultry': ProductCategory.POULTRY,
  'en:fishes': ProductCategory.SEAFOOD,
  'en:seafood': ProductCategory.SEAFOOD,
  'en:fruits': ProductCategory.FRUITS,
  'en:vegetables': ProductCategory.VEGETABLES,
  'en:cereals-and-potatoes': ProductCategory.GRAINS,
  'en:cereals': ProductCategory.GRAINS,
  'en:legumes': ProductCategory.LEGUMES,
  'en:nuts': ProductCategory.NUTS_SEEDS,
  'en:fats': ProductCategory.OILS_FATS,
  'en:sauces': ProductCategory.CONDIMENTS,
  'en:condiments': ProductCategory.CONDIMENTS,
  'en:spices': ProductCategory.SPICES,
  'en:beverages': ProductCategory.BEVERAGES,
  'en:snacks': ProductCategory.SNACKS,
  'en:frozen-foods': ProductCategory.FROZEN,
  'en:canned-foods': ProductCategory.CANNED,
  'en:breads': ProductCategory.BAKERY,
  'en:supplements': ProductCategory.SUPPLEMENTS,
};

function mapCategory(tags: string[] | undefined): ProductCategory {
  if (!tags || tags.length === 0) return ProductCategory.OTHER;
  for (const tag of tags) {
    const category = CATEGORY_MAP[tag];
    if (category) return category;
  }
  return ProductCategory.OTHER;
}

interface OffNutriments {
  'energy-kcal_serving'?: number;
  'energy-kcal_100g'?: number;
  proteins_serving?: number;
  proteins_100g?: number;
  carbohydrates_serving?: number;
  carbohydrates_100g?: number;
  fat_serving?: number;
  fat_100g?: number;
  fiber_serving?: number;
  fiber_100g?: number;
  sugars_serving?: number;
  sugars_100g?: number;
  sodium_serving?: number;
  sodium_100g?: number;
  'saturated-fat_serving'?: number;
  'saturated-fat_100g'?: number;
  cholesterol_serving?: number;
  cholesterol_100g?: number;
}

interface OffProduct {
  product_name?: string;
  brands?: string;
  categories_tags?: string[];
  serving_size?: string;
  serving_quantity?: number;
  nutriments?: OffNutriments;
}

function parseServingSize(servingSize?: string, servingQuantity?: number): number {
  if (servingQuantity && servingQuantity > 0) return servingQuantity;
  if (!servingSize) return 100;
  const match = servingSize.match(/(\d+(?:\.\d+)?)/);
  return match ? parseFloat(match[1]!) : 100;
}

function mapNutrition(nutriments: OffNutriments | undefined, hasServing: boolean) {
  if (!nutriments) {
    return { calories: 0, protein: 0, carbs: 0, fat: 0 };
  }
  const suffix = hasServing ? '_serving' : '_100g';
  const calories =
    nutriments[`energy-kcal${suffix}` as keyof OffNutriments] ??
    nutriments['energy-kcal_100g'] ??
    0;
  const protein =
    nutriments[`proteins${suffix}` as keyof OffNutriments] ?? nutriments['proteins_100g'] ?? 0;
  const carbs =
    nutriments[`carbohydrates${suffix}` as keyof OffNutriments] ??
    nutriments['carbohydrates_100g'] ??
    0;
  const fat = nutriments[`fat${suffix}` as keyof OffNutriments] ?? nutriments['fat_100g'] ?? 0;

  const result: Record<string, number> = {
    calories: Number(calories),
    protein: Number(protein),
    carbs: Number(carbs),
    fat: Number(fat),
  };

  const fiber = nutriments[`fiber${suffix}` as keyof OffNutriments] ?? nutriments['fiber_100g'];
  if (fiber != null) result['fiber'] = Number(fiber);

  const sugars = nutriments[`sugars${suffix}` as keyof OffNutriments] ?? nutriments['sugars_100g'];
  if (sugars != null) result['sugar'] = Number(sugars);

  const sodium = nutriments[`sodium${suffix}` as keyof OffNutriments] ?? nutriments['sodium_100g'];
  if (sodium != null) result['sodium'] = Number(sodium) * 1000; // g → mg

  const saturatedFat =
    nutriments[`saturated-fat${suffix}` as keyof OffNutriments] ?? nutriments['saturated-fat_100g'];
  if (saturatedFat != null) result['saturatedFat'] = Number(saturatedFat);

  const cholesterol =
    nutriments[`cholesterol${suffix}` as keyof OffNutriments] ?? nutriments['cholesterol_100g'];
  if (cholesterol != null) result['cholesterol'] = Number(cholesterol) * 1000; // g → mg

  return result;
}

export class BarcodeService {
  private readonly productsRepository: ProductsRepository;

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

  public async lookup(
    householdId: string,
    barcode: string,
    createdBy: string,
  ): Promise<BarcodeLookupResult> {
    // 1. Check local DB first
    const existing = await this.productsRepository.findByBarcode(householdId, barcode);
    if (existing) {
      return { found: true, product: existing as unknown as Record<string, unknown>, cached: true };
    }

    // 2. Call Open Food Facts API
    try {
      const { statusCode, body } = await request(`${OFF_BASE_URL}/${barcode}`, {
        method: 'GET',
        headersTimeout: TIMEOUT_MS,
        bodyTimeout: TIMEOUT_MS,
        headers: { 'User-Agent': USER_AGENT },
      });

      if (statusCode !== 200) {
        return { found: false };
      }

      const json = (await body.json()) as { status?: number; product?: OffProduct };
      if (!json.product || json.status === 0) {
        return { found: false };
      }

      const offProduct = json.product;
      if (!offProduct.product_name) {
        return { found: false };
      }

      // 3. Map OFF response to Product shape
      const hasServing = Boolean(offProduct.serving_quantity || offProduct.serving_size);
      const servingSize = hasServing
        ? parseServingSize(offProduct.serving_size, offProduct.serving_quantity)
        : 100;

      const nutrition = mapNutrition(offProduct.nutriments, hasServing);
      const brand = offProduct.brands?.split(',')[0]?.trim();

      const productData = {
        householdId,
        name: offProduct.product_name,
        ...(brand ? { brand } : {}),
        barcode,
        category: mapCategory(offProduct.categories_tags),
        servingSize,
        servingUnit: ServingUnit.GRAMS,
        nutrition,
        tags: [] as string[],
        source: ProductSource.BARCODE_LOOKUP,
        createdBy,
      };

      // 4. Cache in local DB
      const saved = await this.productsRepository.create(productData);
      return { found: true, product: saved as unknown as Record<string, unknown>, cached: false };
    } catch {
      return { found: false };
    }
  }
}
