/**
 * GAO Tek Scraper — Canadian compatible transceiver distributor
 *
 * gaotek.com — large catalog with USD prices, WooCommerce-based.
 * Category: /category/fiber-optics/transceivers/
 *
 * Rate limited: 1 req/2sec.
 */
import {
  pool,
  findOrCreateScrapedTransceiver,
  ensureVendor,
  markDetailsVerified,
  markImageVerified,
  upsertPriceObservation,
} from "../utils/db";
import { contentHash } from "../utils/hash";
import * as cheerio from "cheerio";

const BASE = "https://gaotek.com";
const CATALOG_PATH = "/category/fiber-optics/transceivers/";
const MAX_PAGES = 20;
const HEADERS = {
  "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
  Accept: "text/html,application/xhtml+xml",
  "Accept-Language": "en-US,en;q=0.9",
};

interface Product {
  partNumber: string;
  name: string;
  url: string;
  price?: number;
  formFactor: string;
  speed: string;
  speedGbps: number;
  reachLabel?: string;
  reachMeters?: number;
  fiberType?: string;
  wavelength?: string;
  imageUrl?: string;
}

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function detectFormFactor(text: string): { formFactor: string; speed: string; speedGbps: number } {
  const lower = text.toLowerCase();
  if (lower.includes("osfp") && !lower.includes("qsfp")) return { formFactor: "OSFP", speed: "400G", speedGbps: 400 };
  if (lower.includes("qsfp-dd")) return { formFactor: "QSFP-DD", speed: "400G", speedGbps: 400 };
  if (lower.includes("qsfp28")) return { formFactor: "QSFP28", speed: "100G", speedGbps: 100 };
  if (lower.includes("qsfp+") || lower.includes("qsfp plus")) return { formFactor: "QSFP+", speed: "40G", speedGbps: 40 };
  if (lower.includes("sfp56")) return { formFactor: "SFP56", speed: "50G", speedGbps: 50 };
  if (lower.includes("sfp28") || lower.includes("25g")) return { formFactor: "SFP28", speed: "25G", speedGbps: 25 };
  if (lower.includes("sfp+") || lower.includes("10gbase") || lower.includes("10g")) return { formFactor: "SFP+", speed: "10G", speedGbps: 10 };
  if (lower.includes("xfp")) return { formFactor: "XFP", speed: "10G", speedGbps: 10 };
  if (lower.includes("1000base") || lower.includes("1g")) return { formFactor: "SFP", speed: "1G", speedGbps: 1 };
  if (lower.includes("sfp") && !lower.includes("qsfp")) return { formFactor: "SFP", speed: "1G", speedGbps: 1 };
  return { formFactor: "SFP+", speed: "10G", speedGbps: 10 };
}

function detectReach(text: string): { label: string; meters: number } | undefined {
  const patterns: [RegExp, string, number][] = [
    [/\b160\s*km\b/i, "160km", 160000],
    [/\b140\s*km\b/i, "140km", 140000],
    [/\b120\s*km\b/i, "120km", 120000],
    [/\b80\s*km\b/i, "80km", 80000],
    [/\b50\s*km\b/i, "50km", 50000],
    [/\b40\s*km\b/i, "40km", 40000],
    [/\b30\s*km\b/i, "30km", 30000],
    [/\b20\s*km\b/i, "20km", 20000],
    [/\b15\s*km\b/i, "15km", 15000],
    [/\b10\s*km\b/i, "10km", 10000],
    [/\b2\s*km\b/i, "2km", 2000],
    [/\b1\s*km\b/i, "1km", 1000],
    [/\b550\s*m\b/i, "550m", 550],
    [/\b500\s*m\b/i, "500m", 500],
    [/\b300\s*m\b/i, "300m", 300],
    [/\b100\s*m\b/i, "100m", 100],
    [/\b82\s*m\b/i, "82m", 82],
    [/\bLR4\b/, "10km", 10000], [/\bLR\b/, "10km", 10000],
    [/\bER4?\b/, "40km", 40000], [/\bZR4?\b/, "80km", 80000],
    [/\bSR4?\b/, "300m", 300], [/\bDR4?\b/, "500m", 500], [/\bFR4?\b/, "2km", 2000],
  ];
  for (const [regex, label, meters] of patterns) {
    if (regex.test(text)) return { label, meters };
  }
  const km = text.match(/\b(\d+(?:\.\d+)?)\s*km\b/i);
  if (km) {
    const value = Number(km[1]);
    if (value > 0 && value <= 200) return { label: `${Number.isInteger(value) ? value : value.toString()}km`, meters: Math.round(value * 1000) };
  }
  const meters = text.match(/\b(\d+(?:\.\d+)?)\s*m\b/i);
  if (meters) {
    const value = Number(meters[1]);
    if (value > 0 && value <= 10000) return { label: `${Number.isInteger(value) ? value : value.toString()}m`, meters: Math.round(value) };
  }
  const miles = text.match(/\b(\d+(?:\.\d+)?)\s*(?:mi|miles?)\b/i);
  if (miles) {
    const value = Number(miles[1]);
    if (value > 0 && value <= 125) {
      const kmRounded = Math.round(value * 1.609344);
      return { label: `${kmRounded}km`, meters: kmRounded * 1000 };
    }
  }
  return undefined;
}

function detectFiber(text: string): string {
  if (/single.?mode|smf|[^a-z]lx[^a-z]|[^a-z]lr[^a-z]|[^a-z]er[^a-z]|[^a-z]zr[^a-z]|bidi|cwdm|dwdm/i.test(text)) return "SMF";
  if (/multi.?mode|mmf|[^a-z]sx[^a-z]|[^a-z]sr[^a-z]/i.test(text)) return "MMF";
  if (/copper|dac|twinax|rj45|base-t/i.test(text)) return "Copper";
  return "SMF";
}

function detectWavelength(text: string): string {
  const match = text.match(/(\d{3,4})\s*nm/i);
  return match ? match[1] : "";
}

function cleanSku(text: string): string {
  return text.replace(/\s+/g, "").replace(/GAOTek-/i, "GAOTek-").trim();
}

function isProductUrl(url: string): boolean {
  return /^https:\/\/gaotek\.com\/product\/[^/?#]+\/?$/i.test(url);
}

function isNonTransceiverProduct(text: string): boolean {
  return /\b(switch|walkie|radio|tester|meter|microscope|amplifier|oadm|demux|demultiplexer|multiplexer|mux|platform|transponder|transport system|solution|converter|adapter|cleaning|cassette|attenuator|telephone|sensor)\b/i.test(text);
}

function parseProductList(html: string): Product[] {
  const $ = cheerio.load(html);
  const products: Product[] = [];

  // WooCommerce/Woodmart product grid. GAO Tek currently uses Woodmart
  // `.wd-product.product-grid-item` cards rather than classic `li.product`.
  $(".wd-product.product-grid-item, div.product-grid-item, li.product, .product-item, .woocommerce-loop-product, article.product").each((_i, el) => {
    const titleEl = $(el).find(".wd-entities-title a, .woocommerce-loop-product__title, h2, h3, .product-title, .product-name").first();
    const name = titleEl.text().trim();
    if (!name || name.length < 5) return;
    if (isNonTransceiverProduct(name)) return;

    const linkEl = $(el).find("a.wd-product-img-link[href], .wd-entities-title a[href], a[href]").first();
    const href = linkEl.attr("href") || "";
    const url = href.startsWith("http") ? href : BASE + href;
    if (!isProductUrl(url)) return;

    // WooCommerce price
    const priceText = $(el).find(".price, .woocommerce-Price-amount, .amount").text();
    const priceMatch = priceText.match(/\$\s*([\d,]+\.?\d{0,2})/);
    let price: number | undefined;
    if (priceMatch) {
      const parsed = parseFloat(priceMatch[1].replace(",", ""));
      if (parsed > 0 && parsed < 50000) price = parsed;
    }

    // GAO Tek uses SKU for part numbers
    const skuEl = $(el).find(".wd-sku, .sku, [data-sku]");
    const partNumber = cleanSku(skuEl.text()) ||
      url.split("/").filter(Boolean).pop()?.replace(/-/g, " ").trim() ||
      name.match(/[A-Z0-9][-A-Z0-9]{5,}/)?.[0] ||
      name.replace(/\s+/g, "-").slice(0, 60);

    const ff = detectFormFactor(name);
    const reach = detectReach(name);
    const rawImg =
      $(el).find("img").first().attr("data-src") ||
      $(el).find("img").first().attr("data-lazy-src") ||
      $(el).find("img").first().attr("src");
    const imageUrl = rawImg && !/(logo|placeholder|default|no-image|icon|sprite)/i.test(rawImg)
      ? (rawImg.startsWith("http") ? rawImg : BASE + rawImg)
      : undefined;

    products.push({
      partNumber,
      name,
      url,
      price,
      ...ff,
      reachLabel: reach?.label,
      reachMeters: reach?.meters,
      fiberType: detectFiber(name),
      wavelength: detectWavelength(name),
      imageUrl,
    });
  });

  // Fallback for non-WooCommerce layout
  if (products.length === 0) {
    $("a[href*='/product/']").each((_i, el) => {
      const name = $(el).text().trim();
      const href = $(el).attr("href") || "";
      const url = href.startsWith("http") ? href : BASE + href;
      if (
        name.length < 8 || name.length > 200 ||
        !isProductUrl(url) ||
        isNonTransceiverProduct(`${name} ${url}`) ||
        !/sfp|qsfp|xfp|transceiver|optic/i.test(name)
      ) return;

      const context = $(el).parent().parent().text();
      const priceMatch = context.match(/\$\s*([\d,]+\.?\d{0,2})/);
      let price: number | undefined;
      if (priceMatch) {
        const parsed = parseFloat(priceMatch[1].replace(",", ""));
        if (parsed > 0 && parsed < 50000) price = parsed;
      }
      const ff = detectFormFactor(name);
      const reach = detectReach(name);
      const rawImg =
        $(el).find("img").first().attr("data-src") ||
        $(el).find("img").first().attr("data-lazy-src") ||
        $(el).find("img").first().attr("src");
      const imageUrl = rawImg && !/(logo|placeholder|default|no-image|icon|sprite)/i.test(rawImg)
        ? (rawImg.startsWith("http") ? rawImg : BASE + rawImg)
        : undefined;
      products.push({
        partNumber: cleanSku(context.match(/SKU:\s*([A-Z0-9][A-Z0-9\-\s]{4,})/i)?.[1] || "") ||
          name.match(/[A-Z0-9][-A-Z0-9]{5,}/)?.[0] ||
          name.replace(/\s+/g, "-").slice(0, 60),
        name, url, price, ...ff,
        reachLabel: reach?.label, reachMeters: reach?.meters,
        fiberType: detectFiber(name), wavelength: detectWavelength(name),
        imageUrl,
      });
    });
  }

  const seen = new Set<string>();
  return products.filter((p) => {
    if (!p.url || !isProductUrl(p.url) || seen.has(p.url)) return false;
    seen.add(p.url);
    return true;
  });
}

async function fetchPage(url: string): Promise<string> {
  const resp = await fetch(url, { headers: HEADERS, signal: AbortSignal.timeout(30000) });
  if (!resp.ok) throw new Error(`HTTP ${resp.status} for ${url}`);
  return resp.text();
}

async function saveGaoTekProduct(vendorId: string, product: Product): Promise<string> {
  const existingByUrl = await pool.query<{ id: string; part_number: string }>(
    `SELECT id, part_number
     FROM transceivers
     WHERE vendor_id = $1
       AND product_page_url = $2
     LIMIT 1`,
    [vendorId, product.url]
  );

  if (existingByUrl.rows.length > 0) {
    const existing = existingByUrl.rows[0];
    await pool.query(
      `UPDATE transceivers
       SET part_number = CASE
             WHEN $3::text != ''
              AND (part_number IS NULL
                OR part_number = ''
                OR ($3::text ~* '^GAOTek-' AND part_number !~* '^GAOTek-')
                OR part_number LIKE '% %'
                OR length(part_number) > 45
                OR part_number ~* '^(fiber|optical|gbic|sfp|qsfp|cfp).*transceivers?$')
             THEN $3::text
             ELSE part_number
           END,
           product_page_url = COALESCE(NULLIF(product_page_url, ''), NULLIF($2, '')),
           form_factor = COALESCE(NULLIF(form_factor, ''), $4),
           speed_gbps = CASE WHEN speed_gbps IS NULL OR speed_gbps = 0 THEN COALESCE($5, speed_gbps) ELSE speed_gbps END,
           speed = COALESCE(NULLIF(speed, ''), $6),
           reach_meters = CASE WHEN reach_meters IS NULL OR reach_meters = 0 THEN COALESCE($7, reach_meters) ELSE reach_meters END,
           reach_label = COALESCE(NULLIF(reach_label, ''), $8),
           fiber_type = COALESCE(NULLIF(fiber_type, ''), $9),
           wavelengths = COALESCE(NULLIF(wavelengths, ''), $10),
           category = COALESCE(NULLIF(category, ''), $11),
           data_confidence = CASE
             WHEN data_confidence IS NULL OR data_confidence IN ('unknown', 'enriched_estimated')
               THEN 'scraped_unverified'
             ELSE data_confidence
           END,
           updated_at = NOW()
       WHERE id = $1`,
      [
        existing.id,
        product.url,
        product.partNumber,
        product.formFactor || null,
        product.speedGbps || null,
        product.speed || null,
        product.reachMeters || null,
        product.reachLabel || null,
        product.fiberType || null,
        product.wavelength || null,
        "DataCenter",
      ]
    );

    if (product.imageUrl) await markImageVerified(existing.id, product.imageUrl);
    await markDetailsVerified({ transceiverId: existing.id, sourceUrl: product.url });
    return existing.id;
  }

  return findOrCreateScrapedTransceiver({
    partNumber: product.partNumber,
    vendorId,
    productUrl: product.url,
    formFactor: product.formFactor,
    speedGbps: product.speedGbps,
    speed: product.speed,
    reachMeters: product.reachMeters,
    reachLabel: product.reachLabel,
    fiberType: product.fiberType,
    wavelengths: product.wavelength,
    category: "DataCenter",
    imageUrl: product.imageUrl,
  });
}

async function quarantineGaoTekCategoryArtifacts(vendorId: string): Promise<number> {
  const result = await pool.query(
    `UPDATE transceivers
     SET data_confidence = 'unknown',
         price_verified = false,
         image_verified = false,
         details_verified = false,
         fully_verified = false,
         price_verified_at = NULL,
         image_verified_at = NULL,
         details_verified_at = NULL,
         fully_verified_at = NULL,
         updated_at = NOW()
     WHERE vendor_id = $1
       AND (
         product_page_url IS NULL
         OR product_page_url = ''
         OR product_page_url LIKE 'https://gaotek.com/category/%'
         OR part_number ~* '(switch|walkie|radio|tester|meter|microscope|amplifier|oadm|demux|demultiplexer|multiplexer|mux|platform|transponder|transport system|solution|converter|adapter|cleaning|cassette|attenuator|telephone|sensor)'
         OR product_page_url ~* '(switch|walkie|radio|tester|meter|microscope|amplifier|oadm|demux|demultiplexer|multiplexer|mux|platform|transponder|transport-system|solution|converter|adapter|cleaning|cassette|attenuator|telephone|sensor)'
       )
       AND (
         COALESCE(data_confidence, 'unknown') != 'unknown'
         OR price_verified = true
         OR image_verified = true
         OR details_verified = true
         OR fully_verified = true
       )`,
    [vendorId]
  );
  return result.rowCount ?? 0;
}

export async function scrapeGaoTek(): Promise<void> {
  console.log("=== GAO Tek Scraper Starting ===\n");

  const vendorId = await ensureVendor(
    "GAO Tek",
    "compatible",
    "https://gaotek.com",
    BASE + CATALOG_PATH,
  );

  let allProducts: Product[] = [];

  for (let page = 1; page <= MAX_PAGES; page++) {
    try {
      // WooCommerce pagination: /page/N/ suffix
      const url = page === 1
        ? BASE + CATALOG_PATH
        : `${BASE}${CATALOG_PATH}page/${page}/`;
      const html = await fetchPage(url);
      const pageProducts = parseProductList(html);
      allProducts.push(...pageProducts);
      console.log(`  Page ${page}: ${pageProducts.length} products`);
      if (pageProducts.length === 0) {
        console.log(`  Empty page ${page}, stopping pagination.`);
        break;
      }
      if (page < MAX_PAGES) await sleep(2000);
    } catch (err) {
      console.warn(`  Page ${page} failed: ${(err as Error).message}`);
      break;
    }
  }

  // Dedupe
  const seen = new Set<string>();
  allProducts = allProducts.filter((p) => {
    if (seen.has(p.url)) return false;
    seen.add(p.url);
    return true;
  });

  console.log(`\nTotal unique products: ${allProducts.length}`);

  const quarantined = await quarantineGaoTekCategoryArtifacts(vendorId);
  if (quarantined > 0) console.log(`Quarantined ${quarantined} GAO Tek category/non-product artifacts`);

  let totalProducts = 0;
  let priceUpdates = 0;

  for (const product of allProducts) {
    try {
      const txId = await saveGaoTekProduct(vendorId, product);

      if (product.price && product.price > 0) {
        const hash = contentHash(JSON.stringify({ price: product.price, part: product.partNumber }));
        const updated = await upsertPriceObservation({
          transceiverId: txId,
          sourceVendorId: vendorId,
          price: product.price,
          currency: "USD",
          stockLevel: "in_stock",
          url: product.url,
          contentHash: hash,
        });
        if (updated) priceUpdates++;
      }
      totalProducts++;
    } catch (err) {
      console.warn(`  Error saving ${product.partNumber}: ${(err as Error).message.slice(0, 80)}`);
    }
  }

  console.log(`\n=== GAO Tek Complete: ${totalProducts} products, ${priceUpdates} prices ===`);
}

if (require.main === module) {
  scrapeGaoTek()
    .then(() => pool.end())
    .catch((err) => { console.error("Fatal:", err); pool.end(); process.exit(1); });
}
