import { z } from "zod";
import { BRAND } from "@app/shared-types";
import type { Connector, ConnectorContext, ConnectorHealth, ImportResult } from "./types.js";

/**
 * Open Food Facts – öppen produktdatabas (ODbL-licens) för streckkodsuppslag
 * (spec §11 steg 3: "kontrollera tillåten extern datakälla").
 * Attribution krävs enligt licensen och visas i appen.
 */

const offProductSchema = z.object({
  code: z.string(),
  product: z
    .object({
      product_name: z.string().optional(),
      brands: z.string().optional(),
      quantity: z.string().optional(),
      ingredients_text: z.string().optional(),
      allergens_tags: z.array(z.string()).optional(),
      nutriments: z.record(z.string(), z.unknown()).optional(),
      image_url: z.string().optional(),
      lang: z.string().optional(),
    })
    .optional(),
  status: z.number().optional(),
});

export interface OffProduct {
  gtin: string;
  name?: string | undefined;
  brand?: string | undefined;
  quantityText?: string | undefined;
  ingredientsText?: string | undefined;
  allergenTags: string[];
  nutrimentsPer100g: Partial<{
    kcal: number;
    proteinG: number;
    carbsG: number;
    fatG: number;
    saturatedFatG: number;
    fiberG: number;
    sugarG: number;
    saltG: number;
  }>;
  imageUrl?: string | undefined;
}

export class OpenFoodFactsConnector implements Connector<OffProduct> {
  readonly id = "open-food-facts" as const;
  readonly nameSv = "Open Food Facts";
  readonly legalBasis = "open_data" as const;
  readonly attributionSv = "Produktdata: Open Food Facts (ODbL)";

  constructor(
    private readonly baseUrl: string = process.env.OFF_API_URL ?? "https://world.openfoodfacts.org",
    private readonly fetchImpl: typeof fetch = fetch,
  ) {}

  async connect(): Promise<void> {}
  async authenticate(): Promise<boolean> {
    return true;
  }

  /** Slå upp en produkt via GTIN/EAN. */
  async lookupBarcode(gtin: string): Promise<OffProduct | null> {
    const res = await this.fetchImpl(`${this.baseUrl}/api/v2/product/${gtin}.json`, {
      headers: { "user-agent": `${BRAND.name}/0.1 (kontakt: ${BRAND.supportEmail})` },
      signal: AbortSignal.timeout(10_000),
    });
    if (res.status === 404) return null;
    if (!res.ok) throw new Error(`Open Food Facts svarade ${res.status}`);
    const parsed = offProductSchema.safeParse(await res.json());
    if (!parsed.success || !parsed.data.product) return null;
    const p = parsed.data.product;
    const n = (p.nutriments ?? {}) as Record<string, unknown>;
    const num = (key: string): number | undefined => {
      const v = n[key];
      return typeof v === "number" && Number.isFinite(v) ? v : undefined;
    };
    const per100: OffProduct["nutrimentsPer100g"] = {};
    const kcal = num("energy-kcal_100g");
    if (kcal !== undefined) per100.kcal = kcal;
    const protein = num("proteins_100g");
    if (protein !== undefined) per100.proteinG = protein;
    const carbs = num("carbohydrates_100g");
    if (carbs !== undefined) per100.carbsG = carbs;
    const fat = num("fat_100g");
    if (fat !== undefined) per100.fatG = fat;
    const satFat = num("saturated-fat_100g");
    if (satFat !== undefined) per100.saturatedFatG = satFat;
    const fiber = num("fiber_100g");
    if (fiber !== undefined) per100.fiberG = fiber;
    const sugar = num("sugars_100g");
    if (sugar !== undefined) per100.sugarG = sugar;
    const salt = num("salt_100g");
    if (salt !== undefined) per100.saltG = salt;

    return {
      gtin: parsed.data.code,
      name: p.product_name,
      brand: p.brands,
      quantityText: p.quantity,
      ingredientsText: p.ingredients_text,
      allergenTags: p.allergens_tags ?? [],
      nutrimentsPer100g: per100,
      imageUrl: p.image_url,
    };
  }

  async importData(
    _ctx: ConnectorContext,
    params?: Record<string, unknown>,
  ): Promise<ImportResult<OffProduct>> {
    const gtin = typeof params?.gtin === "string" ? params.gtin : null;
    if (!gtin)
      return {
        items: [],
        source: this.id,
        importedAt: new Date().toISOString(),
        warnings: ["gtin saknas"],
      };
    const product = await this.lookupBarcode(gtin);
    return {
      items: product ? [product] : [],
      source: this.id,
      importedAt: new Date().toISOString(),
      warnings: [],
    };
  }

  async sync(ctx: ConnectorContext): Promise<ImportResult<OffProduct>> {
    return this.importData(ctx);
  }
  async disconnect(): Promise<void> {}

  async healthCheck(): Promise<ConnectorHealth> {
    try {
      const res = await this.fetchImpl(`${this.baseUrl}/api/v2/product/7310865004703.json`, {
        headers: { "user-agent": `${BRAND.name}/0.1` },
        signal: AbortSignal.timeout(8_000),
      });
      return {
        ok: res.ok || res.status === 404,
        detail: `status ${res.status}`,
        checkedAt: new Date().toISOString(),
      };
    } catch (err) {
      return {
        ok: false,
        detail: err instanceof Error ? err.message : String(err),
        checkedAt: new Date().toISOString(),
      };
    }
  }
}
