import { writeFile } from "node:fs/promises";
import path from "node:path";

import { chromium } from "playwright";

import { scanOfferContactsByNavigation } from "../playwright/contact-page-navigation.js";
import { isCompletePhoneContactValue } from "../playwright/contact-utils.js";
import { attachVerificationMonitor } from "../playwright/human.js";
import {
  buildSearchBrowserArgs,
  installLowCpuRouteFilters,
} from "../playwright/low-cpu-runtime.js";

import type { LlmMatchedSearchCandidate } from "./fixed-search-llm.ts";
import { buildMerchantKey } from "./multi-stage-results.ts";

const AUTOMATION_DIR = path.resolve(import.meta.dirname!, "..");
const PROJECT_ROOT = path.resolve(AUTOMATION_DIR, "..");

function resolveProfileDir() {
  const explicit = process.env.SEARCH_1688_PROFILE_DIR ?? "";
  if (explicit) {
    return path.isAbsolute(explicit) ? explicit : path.resolve(PROJECT_ROOT, explicit);
  }

  return path.join(AUTOMATION_DIR, ".browser", "1688-profile");
}

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

function filterCompletePhoneContacts(contacts: unknown[]) {
  return contacts.filter((contact) => {
    if (!contact || typeof contact !== "object") {
      return false;
    }

    const record = contact as Record<string, unknown>;
    return isCompletePhoneContactValue(record.type, record.value);
  });
}

export type EnrichedSelectedOffer = {
  index: number;
  input: string;
  "title-text": string;
  url: string;
  score: number;
  is_match: boolean;
  reason: string;
  search_round: number;
  round_label: string;
  title: string;
  offer_url: string;
  image_url: string;
  merchant_name: string;
  merchant_url: string;
  wangwang_url: string;
  offer_screenshot_path: string;
  contact_page_url: string;
  detected_contacts: unknown[];
  has_phone_contact: boolean;
  qualification_status: string;
  contact_collection_reason: string;
};

export type ContactEnrichmentAttempt = {
  candidate_index: number;
  phase: "initial_window" | "top_up";
  input: string;
  "title-text": string;
  url: string;
  score: number;
  is_match: boolean;
  reason: string;
  search_round: number;
  round_label: string;
  screenshot_path: string;
  contact_page_url: string;
  contactPageUrl: string;
  detected_contacts: unknown[];
  detail_contacts: unknown[];
  contact_page_contacts: unknown[];
  has_phone_contact: boolean;
  offerScreenshotStatus: string;
  contactFetchStatus: string;
  contactFetchMethod: string;
  wangwang_url: string;
  wangwang_original_url: string;
  wangwangFetchStatus: string;
  wangwangFetchMethod: string;
  wangwangFetchReason: string;
  qualification_status: "qualified" | "rejected_no_phone" | "rejected_contact_error" | "rejected_duplicate_merchant";
  contact_collection_reason: string;
  error_message: string;
  inserted_rank: number | null;
};

export type ContactEnrichmentResult = {
  enriched: EnrichedSelectedOffer[];
  attempts: ContactEnrichmentAttempt[];
  attemptedCount: number;
  attemptedCandidateCount: number;
  qualifiedCount: number;
  duplicateMerchantCount: number;
  targetQualifiedCount: number | null;
  initialWindowSize: number;
  shortageReason: string;
};

function normalizeCandidateSourceProduct(candidate: Partial<LlmMatchedSearchCandidate> & Record<string, unknown>) {
  const source = candidate.source_product && typeof candidate.source_product === "object"
    ? candidate.source_product as Record<string, unknown>
    : {};
  return {
    title: normalizeSpace(source.title ?? candidate["title-text"] ?? candidate.title),
    offer_url: normalizeSpace(source.offer_url ?? candidate.url ?? candidate.offer_url),
    merchant_name: normalizeSpace(source.merchant_name ?? candidate.merchant_name),
    merchant_url: normalizeSpace(source.merchant_url ?? candidate.merchant_url),
    image_url: normalizeSpace(source.image_url ?? candidate.image_url),
    wangwang_url: normalizeSpace(source.wangwang_url ?? candidate.wangwang_url),
    contact_page_url: normalizeSpace(source.contact_page_url ?? candidate.contact_page_url),
    offer_screenshot_path: normalizeSpace(source.offer_screenshot_path ?? candidate.offer_screenshot_path),
  };
}

export function normalizeLlmMatchJsonCandidates(raw: unknown): LlmMatchedSearchCandidate[] {
  const items = Array.isArray(raw)
    ? raw
    : raw && typeof raw === "object" && Array.isArray((raw as Record<string, unknown>).results)
      ? (raw as Record<string, unknown>).results
      : [];

  return items
    .filter((item): item is Record<string, unknown> => Boolean(item) && typeof item === "object")
    .map((item) => {
      const sourceProduct = normalizeCandidateSourceProduct(item);
      const url = normalizeSpace(item.url ?? sourceProduct.offer_url);
      const title = normalizeSpace(item["title-text"] ?? item.title ?? sourceProduct.title);
      return {
        input: normalizeSpace(item.input),
        "title-text": title,
        url,
        score: Number.isFinite(Number(item.score)) ? Number(item.score) : 0,
        is_match: Boolean(item.is_match),
        reason: normalizeSpace(item.reason),
        search_round: Number.isFinite(Number(item.search_round)) ? Number(item.search_round) : 0,
        round_label: normalizeSpace(item.round_label),
        source_product: {
          ...sourceProduct,
          title: sourceProduct.title || title,
          offer_url: sourceProduct.offer_url || url,
        },
      } satisfies LlmMatchedSearchCandidate;
    })
    .filter((candidate) => candidate.url && candidate["title-text"]);
}

export async function enrichSelectedOfferCandidates(params: {
  candidates: LlmMatchedSearchCandidate[];
  screenshotDir: string;
  headless: boolean;
  timeoutMs?: number;
  targetQualifiedCount?: number;
  auditPath?: string;
  onCandidateAttempt?: (progress: {
    candidateIndex: number;
    attemptedCount: number;
    qualifiedCount: number;
    targetQualifiedCount: number | null;
    candidate: LlmMatchedSearchCandidate;
    status: "started" | "qualified" | "rejected_no_phone" | "rejected_contact_error";
    reason: string;
  }) => void | Promise<void>;
}): Promise<ContactEnrichmentResult> {
  const targetQualifiedCount = Number.isInteger(params.targetQualifiedCount)
    && params.targetQualifiedCount != null
    && params.targetQualifiedCount > 0
    ? params.targetQualifiedCount
    : null;

  if (params.candidates.length === 0) {
    const emptyResult: ContactEnrichmentResult = {
      enriched: [],
      attempts: [],
      attemptedCount: 0,
      attemptedCandidateCount: 0,
      qualifiedCount: 0,
      duplicateMerchantCount: 0,
      targetQualifiedCount,
      initialWindowSize: 0,
      shortageReason: targetQualifiedCount ? "matched_candidates_exhausted_or_valid_phone_insufficient" : "",
    };
    if (params.auditPath) {
      await writeFile(params.auditPath, JSON.stringify(emptyResult, null, 2), "utf8");
    }
    return emptyResult;
  }

  const context = await chromium.launchPersistentContext(resolveProfileDir(), {
    channel: "chrome",
    headless: params.headless,
    locale: "zh-CN",
    viewport: { width: 1440, height: 960 },
    args: buildSearchBrowserArgs(),
  });
  await installLowCpuRouteFilters(context);
  const verificationMonitor = attachVerificationMonitor(context, { logPrefix: "[fixed-runtime][验证码]" });

  try {
    const enriched: EnrichedSelectedOffer[] = [];
    const attempts: ContactEnrichmentAttempt[] = [];
    const acceptedMerchantKeys = new Set<string>();
    let duplicateMerchantCount = 0;
    const initialWindowSize = targetQualifiedCount == null
      ? params.candidates.length
      : Math.min(targetQualifiedCount, params.candidates.length);

    const processCandidate = async (
      candidate: LlmMatchedSearchCandidate,
      candidateIndex: number,
      phase: ContactEnrichmentAttempt["phase"],
    ) => {
      if (targetQualifiedCount != null && enriched.length >= targetQualifiedCount) {
        return false;
      }

      const candidateMerchantKey = buildMerchantKey({
        merchant_url: candidate.source_product.merchant_url,
        contact_page_url: candidate.source_product.contact_page_url,
        merchant_name: candidate.source_product.merchant_name,
        wangwang_url: candidate.source_product.wangwang_url,
        offer_url: candidate.source_product.offer_url || candidate.url,
        title: candidate.source_product.title || candidate["title-text"],
      });
      if (candidateMerchantKey && acceptedMerchantKeys.has(candidateMerchantKey)) {
        duplicateMerchantCount += 1;
        attempts.push({
          candidate_index: candidateIndex + 1,
          phase,
          input: candidate.input,
          "title-text": candidate["title-text"],
          url: candidate.url,
          score: candidate.score,
          is_match: candidate.is_match,
          reason: candidate.reason,
          search_round: candidate.search_round,
          round_label: candidate.round_label,
          screenshot_path: "",
          contact_page_url: candidate.source_product.contact_page_url,
          contactPageUrl: candidate.source_product.contact_page_url,
          detected_contacts: [],
          detail_contacts: [],
          contact_page_contacts: [],
          has_phone_contact: false,
          offerScreenshotStatus: "skipped",
          contactFetchStatus: "skipped",
          contactFetchMethod: "",
          wangwang_url: candidate.source_product.wangwang_url,
          wangwang_original_url: candidate.source_product.wangwang_url,
          wangwangFetchStatus: "skipped",
          wangwangFetchMethod: "",
          wangwangFetchReason: "duplicate_merchant",
          qualification_status: "rejected_duplicate_merchant",
          contact_collection_reason: "duplicate_merchant",
          error_message: "",
          inserted_rank: null,
        });
        return true;
      }

      const screenshotPath = path.join(params.screenshotDir, `offer-${candidateIndex + 1}.jpg`);
      await params.onCandidateAttempt?.({
        candidateIndex,
        attemptedCount: attempts.length + 1,
        qualifiedCount: enriched.length,
        targetQualifiedCount,
        candidate,
        status: "started",
        reason: "",
      });

      let navigationScan: {
        detectedContacts?: unknown;
        preferredTitle?: unknown;
        screenshotPath?: unknown;
        contactPageUrl?: unknown;
        detailContacts?: unknown;
        contactPageContacts?: unknown;
        status?: unknown;
        reason?: unknown;
        offerScreenshotStatus?: unknown;
        contactFetchStatus?: unknown;
        contactFetchMethod?: unknown;
        wangwangUrl?: unknown;
        wangwangFetchStatus?: unknown;
        wangwangFetchMethod?: unknown;
        wangwangFetchReason?: unknown;
      } = {};
      let errorMessage = "";
      try {
        navigationScan = await scanOfferContactsByNavigation(context, {
          offerUrl: candidate.url,
          timeoutMs: params.timeoutMs ?? 8000,
          screenshotPath,
          merchantName: candidate.source_product.merchant_name,
          merchantUrl: candidate.source_product.merchant_url,
          contactPageUrl: candidate.source_product.contact_page_url,
          currentTitle: candidate["title-text"],
        });
      } catch (error) {
        errorMessage = error instanceof Error ? error.message : String(error);
      }

      const detectedContacts = Array.isArray(navigationScan.detectedContacts)
        ? navigationScan.detectedContacts
        : [];
      const detailContacts = Array.isArray(navigationScan.detailContacts)
        ? navigationScan.detailContacts
        : [];
      const contactPageContacts = Array.isArray(navigationScan.contactPageContacts)
        ? navigationScan.contactPageContacts
        : [];
      const qualifyingPhoneContacts = filterCompletePhoneContacts(contactPageContacts);
      const hasPhoneContact = qualifyingPhoneContacts.length > 0;
      const contactCollectionReason = normalizeSpace(navigationScan.reason) || errorMessage;
      const offerScreenshotStatus = normalizeSpace(navigationScan.offerScreenshotStatus);
      const offerScreenshotPath = offerScreenshotStatus === "ok"
        ? normalizeSpace(navigationScan.screenshotPath)
        : "";
      const originalWangwangUrl = candidate.source_product.wangwang_url;
      const resolvedWangwangUrl = normalizeSpace(navigationScan.wangwangUrl) || originalWangwangUrl;
      const baseAttempt = {
        candidate_index: candidateIndex + 1,
        phase,
        input: candidate.input,
        "title-text": candidate["title-text"],
        url: candidate.url,
        score: candidate.score,
        is_match: candidate.is_match,
        reason: candidate.reason,
        search_round: candidate.search_round,
        round_label: candidate.round_label,
        screenshot_path: offerScreenshotPath,
        contact_page_url: normalizeSpace(navigationScan.contactPageUrl) || candidate.source_product.contact_page_url,
        contactPageUrl: normalizeSpace(navigationScan.contactPageUrl) || candidate.source_product.contact_page_url,
        detected_contacts: detectedContacts,
        detail_contacts: detailContacts,
        contact_page_contacts: contactPageContacts,
        has_phone_contact: hasPhoneContact,
        offerScreenshotStatus,
        contactFetchStatus: normalizeSpace(navigationScan.contactFetchStatus),
        contactFetchMethod: normalizeSpace(navigationScan.contactFetchMethod),
        wangwang_url: resolvedWangwangUrl,
        wangwang_original_url: originalWangwangUrl,
        wangwangFetchStatus: normalizeSpace(navigationScan.wangwangFetchStatus),
        wangwangFetchMethod: normalizeSpace(navigationScan.wangwangFetchMethod),
        wangwangFetchReason: normalizeSpace(navigationScan.wangwangFetchReason),
        contact_collection_reason: contactCollectionReason,
        error_message: errorMessage,
      };

      if (!hasPhoneContact) {
        const scanStatus = normalizeSpace(navigationScan.status);
        const status = errorMessage || (scanStatus && scanStatus !== "ok" && scanStatus !== "skipped")
          ? "rejected_contact_error"
          : "rejected_no_phone";
        attempts.push({
          ...baseAttempt,
          qualification_status: status,
          inserted_rank: null,
        });
        await params.onCandidateAttempt?.({
          candidateIndex,
          attemptedCount: attempts.length,
          qualifiedCount: enriched.length,
          targetQualifiedCount,
          candidate,
          status,
          reason: contactCollectionReason,
        });
        return true;
      }

      const enrichedOffer = {
        index: enriched.length + 1,
        input: candidate.input,
        "title-text": candidate["title-text"],
        url: candidate.url,
        score: candidate.score,
        is_match: candidate.is_match,
        reason: candidate.reason,
        search_round: candidate.search_round,
        round_label: candidate.round_label,
        title: candidate.source_product.title || candidate["title-text"],
        offer_url: candidate.source_product.offer_url || candidate.url,
        image_url: candidate.source_product.image_url,
        merchant_name: candidate.source_product.merchant_name,
        merchant_url: candidate.source_product.merchant_url,
        wangwang_url: resolvedWangwangUrl,
        offer_screenshot_path: offerScreenshotPath || candidate.source_product.offer_screenshot_path,
        contact_page_url: normalizeSpace(navigationScan.contactPageUrl) || candidate.source_product.contact_page_url,
        detected_contacts: qualifyingPhoneContacts,
        has_phone_contact: true,
        qualification_status: "qualified",
        contact_collection_reason: normalizeSpace(navigationScan.reason),
      } satisfies EnrichedSelectedOffer;
      enriched.push(enrichedOffer);
      if (candidateMerchantKey) {
        acceptedMerchantKeys.add(candidateMerchantKey);
      }
      attempts.push({
        ...baseAttempt,
        qualification_status: "qualified",
        inserted_rank: enrichedOffer.index,
      });
      await params.onCandidateAttempt?.({
        candidateIndex,
        attemptedCount: attempts.length,
        qualifiedCount: enriched.length,
        targetQualifiedCount,
        candidate,
        status: "qualified",
        reason: contactCollectionReason,
      });
      return true;
    };

    for (let candidateIndex = 0; candidateIndex < initialWindowSize; candidateIndex += 1) {
      await processCandidate(params.candidates[candidateIndex]!, candidateIndex, "initial_window");
    }

    for (let candidateIndex = initialWindowSize; candidateIndex < params.candidates.length; candidateIndex += 1) {
      if (targetQualifiedCount != null && enriched.length >= targetQualifiedCount) {
        break;
      }
      await processCandidate(params.candidates[candidateIndex]!, candidateIndex, "top_up");
    }

    const result: ContactEnrichmentResult = {
      enriched,
      attempts,
      attemptedCount: attempts.length,
      attemptedCandidateCount: attempts.length,
      qualifiedCount: enriched.length,
      duplicateMerchantCount,
      targetQualifiedCount,
      initialWindowSize,
      shortageReason: targetQualifiedCount != null && enriched.length < targetQualifiedCount
        ? (duplicateMerchantCount > 0
            ? "duplicate_merchants_exhausted"
            : "matched_candidates_exhausted_or_valid_phone_insufficient")
        : "",
    };

    if (params.auditPath) {
      await writeFile(params.auditPath, JSON.stringify(result, null, 2), "utf8");
    }

    return result;
  } finally {
    verificationMonitor.dispose();
    await context.close().catch(() => {});
  }
}
