/**
 * Repair suspicious final_result_products.title values from original search_rounds.results.
 *
 * Dry run by default:
 *   pnpm exec tsx automation/pipelines/repair-final-result-product-titles.ts --batch-id=CG-20260424-001
 *
 * Apply updates:
 *   pnpm exec tsx automation/pipelines/repair-final-result-product-titles.ts --batch-id=CG-20260424-001 --apply
 */

import "dotenv/config";

import process from "node:process";

import { createResolvedSupabaseClient } from "../internal/supabase-node-client.js";
import {
  looksLikeGeneratedResourceTitle,
  sanitizeSearchResultTitle,
} from "../playwright/search-result-extractor.js";

type RepairOptions = {
  apply: boolean;
  batchId: string;
  materialId: string;
  taskId: string;
  limit: number;
};

type FinalResultProductRow = {
  id: string;
  batch_id: string;
  material_id: string;
  task_id: string | null;
  offer_url: string;
  title: string;
};

type SearchRoundRow = {
  batch_id: string;
  material_id: string;
  task_id: string | null;
  results: unknown;
};

function normalizeText(value: unknown) {
  return String(value ?? "").replace(/\s+/g, " ").trim();
}

function normalizeOfferUrlForLookup(value: unknown) {
  const raw = normalizeText(value);
  if (!raw) return "";

  try {
    const parsed = new URL(raw.startsWith("//") ? `https:${raw}` : raw);
    const offerIdMatch = parsed.pathname.match(/\/offer\/(\d+)\.html/i);
    const offerId = offerIdMatch?.[1] || parsed.searchParams.get("offerId") || parsed.searchParams.get("id") || "";
    if (offerId) {
      return `https://detail.1688.com/offer/${offerId}.html`;
    }
    parsed.search = "";
    parsed.hash = "";
    return parsed.toString();
  } catch {
    return raw.split(/[?#]/, 1)[0] ?? raw;
  }
}

function isSuspiciousStoredTitle(value: unknown) {
  const title = normalizeText(value);
  if (!title) return false;
  if (looksLikeGeneratedResourceTitle(title)) return true;
  if (/^(?:https?:)?\/\//i.test(title)) return true;
  if (/^data:[a-z]+\/[a-z0-9.+-]+;base64,/i.test(title)) return true;
  return false;
}

function safeTitleCandidate(value: unknown) {
  const title = sanitizeSearchResultTitle(value);
  return title && !isSuspiciousStoredTitle(title) ? title : "";
}

function getSourceProduct(product: Record<string, unknown>) {
  return product.source_product && typeof product.source_product === "object"
    ? product.source_product as Record<string, unknown>
    : {};
}

function extractRoundProducts(results: unknown) {
  if (Array.isArray(results)) {
    return results.filter((item): item is Record<string, unknown> => Boolean(item) && typeof item === "object");
  }

  if (results && typeof results === "object") {
    const nested = (results as Record<string, unknown>).results;
    if (Array.isArray(nested)) {
      return nested.filter((item): item is Record<string, unknown> => Boolean(item) && typeof item === "object");
    }
  }

  return [];
}

function scopeKey(row: Pick<FinalResultProductRow | SearchRoundRow, "batch_id" | "material_id" | "task_id">) {
  return `${row.batch_id}\u0000${row.material_id}\u0000${row.task_id ?? ""}`;
}

function parseArgs(argv: string[]): RepairOptions {
  const options: RepairOptions = {
    apply: false,
    batchId: "",
    materialId: "",
    taskId: "",
    limit: 1000,
  };

  for (const arg of argv) {
    if (arg === "--apply") {
      options.apply = true;
    } else if (arg.startsWith("--batch-id=")) {
      options.batchId = arg.slice("--batch-id=".length).trim();
    } else if (arg.startsWith("--material-id=")) {
      options.materialId = arg.slice("--material-id=".length).trim();
    } else if (arg.startsWith("--task-id=")) {
      options.taskId = arg.slice("--task-id=".length).trim();
    } else if (arg.startsWith("--limit=")) {
      const limit = Number(arg.slice("--limit=".length));
      options.limit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : options.limit;
    }
  }

  return options;
}

async function fetchFinalRows(options: RepairOptions) {
  const supabase = createResolvedSupabaseClient(process.env);
  let query = supabase
    .from("final_result_products")
    .select("id,batch_id,material_id,task_id,offer_url,title")
    .order("created_at", { ascending: false })
    .limit(options.limit);

  if (options.batchId) query = query.eq("batch_id", options.batchId);
  if (options.materialId) query = query.eq("material_id", options.materialId);
  if (options.taskId) query = query.eq("task_id", options.taskId);

  const { data, error } = await query;
  if (error) {
    throw new Error(`[final_result_products] 查询失败: ${error.message}`);
  }

  return (data ?? []) as FinalResultProductRow[];
}

async function fetchSearchRoundsForScopes(scopes: FinalResultProductRow[]) {
  const supabase = createResolvedSupabaseClient(process.env);
  const uniqueScopes = new Map<string, FinalResultProductRow>();
  for (const row of scopes) {
    uniqueScopes.set(scopeKey(row), row);
  }

  const rounds: SearchRoundRow[] = [];
  for (const row of uniqueScopes.values()) {
    let query = supabase
      .from("search_rounds")
      .select("batch_id,material_id,task_id,results")
      .eq("batch_id", row.batch_id)
      .eq("material_id", row.material_id);
    if (row.task_id) {
      query = query.eq("task_id", row.task_id);
    }

    const { data, error } = await query;
    if (error) {
      console.warn(`[search_rounds] 查询失败 batch=${row.batch_id} material=${row.material_id}: ${error.message}`);
      continue;
    }
    rounds.push(...((data ?? []) as SearchRoundRow[]));
  }

  return rounds;
}

function buildReplacementLookup(rounds: SearchRoundRow[]) {
  const lookup = new Map<string, string>();

  for (const round of rounds) {
    const scope = scopeKey(round);
    for (const product of extractRoundProducts(round.results)) {
      const sourceProduct = getSourceProduct(product);
      const offerUrl = normalizeOfferUrlForLookup(product.offer_url ?? product.offerUrl ?? product.url ?? sourceProduct.offer_url);
      if (!offerUrl) continue;

      const title = safeTitleCandidate(product.title)
        || safeTitleCandidate(product["title-text"])
        || safeTitleCandidate(sourceProduct.title);
      if (title && !lookup.has(`${scope}\u0000${offerUrl}`)) {
        lookup.set(`${scope}\u0000${offerUrl}`, title);
      }
    }
  }

  return lookup;
}

async function main() {
  const options = parseArgs(process.argv.slice(2));
  const finalRows = await fetchFinalRows(options);
  const suspiciousRows = finalRows.filter((row) => isSuspiciousStoredTitle(row.title));

  if (suspiciousRows.length === 0) {
    console.log(`[repair-title] 未发现疑似脏标题。扫描 ${finalRows.length} 行。`);
    return;
  }

  const rounds = await fetchSearchRoundsForScopes(suspiciousRows);
  const replacements = buildReplacementLookup(rounds);
  const supabase = createResolvedSupabaseClient(process.env);
  let repaired = 0;
  let unresolved = 0;

  for (const row of suspiciousRows) {
    const offerUrl = normalizeOfferUrlForLookup(row.offer_url);
    const replacement = replacements.get(`${scopeKey(row)}\u0000${offerUrl}`) ?? "";

    if (!replacement) {
      unresolved += 1;
      console.warn(`[repair-title] 无法自动恢复 id=${row.id} batch=${row.batch_id} material=${row.material_id} offer=${row.offer_url}`);
      continue;
    }

    console.log(`[repair-title] ${options.apply ? "更新" : "dry-run"} id=${row.id}: "${row.title}" -> "${replacement}"`);
    if (options.apply) {
      const { error } = await supabase
        .from("final_result_products")
        .update({ title: replacement })
        .eq("id", row.id);
      if (error) {
        throw new Error(`[final_result_products] 更新失败 id=${row.id}: ${error.message}`);
      }
    }
    repaired += 1;
  }

  console.log(`[repair-title] 完成。扫描=${finalRows.length} 疑似=${suspiciousRows.length} ${options.apply ? "已更新" : "可更新"}=${repaired} 无法恢复=${unresolved}`);
  if (!options.apply && repaired > 0) {
    console.log("[repair-title] 当前为 dry-run；确认后追加 --apply 执行更新。");
  }
}

main().catch((error) => {
  console.error(error instanceof Error ? error.message : String(error));
  process.exitCode = 1;
});
