import type { FetchContext, NormalizedQuery, Offer, OfferAttribute, StoreAdapter } from "../core/types.js";
import { validPrice } from "./util.js";

// Trodo.lv — Magento 2. Its search API is gated behind Cloudflare's interactive
// (managed) challenge, so this is a strictly-private, on-demand, human-cleared store
// (see WALLED-STORES.md and MONETIZATION.md §7). We fetch through the resident browser
// (BrowserHub) so a human-earned cf_clearance rides the same-origin request.
//
// The store's own SPA does a text search via:
//   GET /rest/V1/catalogsearch/result/0?q=<CANON>&searchby=name&is_ajax=true
//       &product_list_order=_score&product_list_dir=desc
//       &customer_group_id=0&currency=EUR&list-type=list&country=LV
// Response envelope: [ { products: [...], pagination, filters, ... } ]. Like Handler,
// page 1 returns the searched part plus cross-brand analogues, each with its own price;
// exact-number ranking (upstream) floats the searched OEM to the top.
// Fixture: fixtures/trodo/0986452041.json (recorded 2026-07-12 through a solved session).

const HOME = "https://www.trodo.lv";
const IMG_BASE = "https://picdn.trodo.com/media/m2_catalog_cache/240x240";

const SEARCH = (canonical: string): string =>
  `${HOME}/rest/V1/catalogsearch/result/0?q=${encodeURIComponent(canonical)}` +
  `&searchby=name&product_list_order=_score&product_list_dir=desc&is_ajax=true` +
  `&customer_group_id=0&currency=EUR&list-type=list&country=LV`;

// The human-facing results page for the same query. Navigating here on a cold session
// renders Cloudflare's inline "Verify you are human" widget, which the user solves in
// the noVNC stream; the API SEARCH() above then returns JSON on the retry.
const RESULTS_PAGE = (canonical: string): string =>
  `${HOME}/catalogsearch/result?q=${encodeURIComponent(canonical)}&searchby=name`;

interface TrodoCriterion {
  label?: string;
  value?: string;
}
interface TrodoProduct {
  name?: string;
  url_path?: string;
  url_key?: string;
  final_price?: number;
  price?: number;
  manufacturer?: string;
  tecdoc_sku?: string;
  sku?: string;
  in_stock?: boolean;
  availability_status?: string;
  delivery_date?: string;
  media?: { src?: string };
  label?: { brand?: { name?: string } };
  attributes?: { criteria?: TrodoCriterion[] };
}
type TrodoEnvelope = Array<{ products?: TrodoProduct[] }>;

export const trodo: StoreAdapter = {
  id: "trodo",
  displayName: "Trodo.lv",
  storeHomepage: HOME,
  enabled: true,
  fetchMode: "onDemand",
  timeoutMs: 30000, // includes a possible page navigation / challenge check

  async fetchRaw(q: NormalizedQuery, ctx: FetchContext): Promise<string> {
    // Walled: fetched through the resident Chrome so the human-earned cf_clearance
    // and matching TLS fingerprint ride the request.
    return ctx.browser.fetchJson(this.id, HOME, SEARCH(q.canonical), {
      solvePage: RESULTS_PAGE(q.canonical),
    });
  },

  parse(json: string): Offer[] {
    let envelope: TrodoEnvelope;
    try {
      envelope = JSON.parse(json) as TrodoEnvelope;
    } catch {
      throw new Error("trodo: search response was not JSON");
    }
    if (!Array.isArray(envelope) || envelope.length === 0 || !Array.isArray(envelope[0]?.products)) {
      // A valid no-match response retains the normal [{ products: [] }] envelope. A bare
      // [] or another shape can occur while the browser challenge/session is still settling
      // and must not be presented as a successful zero-result search.
      throw new Error("trodo: unexpected search response envelope");
    }
    const products = envelope[0].products;

    const offers: Offer[] = [];
    for (const p of products) {
      const priceEur = typeof p.final_price === "number" ? p.final_price : Number(p.price);
      if (!validPrice(priceEur)) continue;

      const partNumber = (p.tecdoc_sku || p.sku || "").trim();
      if (!partNumber) continue;

      const slug = (p.url_path || p.url_key || "").replace(/^\/+/, "");
      const brand = (p.label?.brand?.name || p.manufacturer || "").trim() || null;
      const stockKnown = typeof p.in_stock === "boolean" || !!p.availability_status;
      const inStock = p.in_stock === true || p.availability_status === "in-stock";

      const attributes: OfferAttribute[] = (p.attributes?.criteria ?? [])
        .filter((c) => c.label && c.value)
        .slice(0, 8)
        .map((c) => ({ label: c.label!.trim(), value: c.value!.trim() }));

      offers.push({
        store: trodo.id,
        brand,
        partNumber,
        title: p.name?.trim() || partNumber,
        priceEur,
        inStock: stockKnown ? inStock : null,
        availability: inStock ? "Pieejams" : null,
        deliveryNote: p.delivery_date ? `Piegāde: ${p.delivery_date}` : null,
        url: slug ? `${HOME}/${slug}` : HOME,
        imageUrl: p.media?.src ? `${IMG_BASE}${p.media.src}` : null,
        attributes,
      });
    }

    return offers;
  },
};
