import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';

import { afterEach, describe, expect, it } from 'vitest';

import {
  buildSearchTaskStoragePrefix,
  buildFixedRuntimeLlmContext,
  buildFixedRuntimeDiagnosticsSummary,
  resolveFixedRuntimeSearchBlockingReason,
  dedupeFinalResultProductRowsByMerchant,
  getKeywordStageSearchRoundIncrement,
  isUnsafeProductTitle,
  resolveFinalResultProductTitle,
  resolveImageStageOutcome,
  resolveKeywordStagePlaceholderReason,
  shouldFailFixedRuntimeSearchBlockingReason,
  splitFixedRuntimeCandidatesByMatchBucket,
} from './row-pipeline-sync.ts';

const tempDirs: string[] = [];
const ROW_PIPELINE_SYNC_PATH = path.join(import.meta.dirname, 'row-pipeline-sync.ts');

afterEach(async () => {
  await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});

describe('buildSearchTaskStoragePrefix', () => {
  it('scopes screenshots by search task before material', () => {
    expect(buildSearchTaskStoragePrefix('mat-1', 'BATCH-001-ss-002')).toBe('search-task-BATCH-001-ss-002/material-mat-1');
  });

  it('uses the legacy task scope when task_id is missing', () => {
    expect(buildSearchTaskStoragePrefix('mat-1', '')).toBe('search-task-legacy/material-mat-1');
  });
});

describe('fixed runtime contact enrichment session', () => {
  it('passes the active 1688 profile explicitly into contact enrichment', async () => {
    const source = await readFile(ROW_PIPELINE_SYNC_PATH, 'utf8');
    const resolverIndex = source.indexOf('const contactProfileDir = resolve1688ProfileDir()');
    const matchedCallIndex = source.indexOf('profileDir: contactProfileDir', resolverIndex);
    const possibleCallIndex = source.indexOf('profileDir: contactProfileDir', matchedCallIndex + 1);

    expect(source).toContain('from "../playwright/profile-dir.js"');
    expect(resolverIndex).toBeGreaterThanOrEqual(0);
    expect(matchedCallIndex).toBeGreaterThan(resolverIndex);
    expect(possibleCallIndex).toBeGreaterThan(matchedCallIndex);
  });

  it('does not keep auditing possible merchants after the matched merchant target is already full', async () => {
    const source = await readFile(ROW_PIPELINE_SYNC_PATH, 'utf8');
    const remainingLimitIndex = source.indexOf('const remainingPossibleTargetQualifiedCount');
    const possibleCallIndex = source.indexOf('targetQualifiedCount: remainingPossibleTargetQualifiedCount', remainingLimitIndex);

    expect(remainingLimitIndex).toBeGreaterThanOrEqual(0);
    expect(possibleCallIndex).toBeGreaterThan(remainingLimitIndex);
    expect(source).toContain('targetQualifiedCount != null && contactEnrichmentResult.qualifiedCount >= targetQualifiedCount');
  });

  it('keeps possible merchant enrichment failures as shortage diagnostics instead of failing the material', async () => {
    const source = await readFile(ROW_PIPELINE_SYNC_PATH, 'utf8');

    expect(source).toContain('async function enrichPossibleContactCandidates');
    expect(source).toContain('possible_contact_enrichment_failed');
    expect(source).toContain('可能商家联系方式补齐失败，保留已获得商家继续收尾');
  });
});

describe('getKeywordStageSearchRoundIncrement', () => {
  it('uses searchPlanRows when available', () => {
    expect(getKeywordStageSearchRoundIncrement({
      keywordResult: { searchPlanRows: [{}, {}, {}] },
      initialRounds: [{}],
    })).toBe(3);
  });

  it('falls back to initialRounds when searchPlanRows is missing', () => {
    expect(getKeywordStageSearchRoundIncrement({
      keywordResult: {},
      initialRounds: [{}, {}],
    })).toBe(2);
  });

  it('always returns at least one round', () => {
    expect(getKeywordStageSearchRoundIncrement({
      keywordResult: {},
      initialRounds: [],
    })).toBe(1);
  });

  it('marks captcha-intercepted keyword stages as placeholders', () => {
    expect(resolveKeywordStagePlaceholderReason({
      validRound: null,
      initialRounds: [{ round_failure_reason: 'captcha_intercept' }],
    } as never)).toBe('captcha_intercept');
  });

  it('ignores generic round_failed placeholder reasons', () => {
    expect(resolveKeywordStagePlaceholderReason({
      validRound: null,
      initialRounds: [{ round_failure_reason: 'round_failed' }],
    } as never)).toBe('');
  });

  it('marks browser target crashes as placeholder reasons', () => {
    expect(resolveKeywordStagePlaceholderReason({
      validRound: null,
      initialRounds: [{ round_failure_reason: 'browser_target_crashed' }],
    } as never)).toBe('browser_target_crashed');
  });

  it('treats actual login-intercepted rounds as fixed runtime blockers even when planned rounds are successful', () => {
    expect(resolveFixedRuntimeSearchBlockingReason([
      {
        search_round: 1,
        primary_keyword: '球墨铸铁井盖',
        results_path: '',
        round_failure_reason: 'login_intercept',
      },
      {
        search_round: 2,
        primary_keyword: '铸铁井盖',
        results_path: '',
        round_failure_reason: 'login_intercept',
      },
    ] as never)).toBe('login_intercept');
  });

  it('does not treat true empty successful search rounds as login interception', () => {
    expect(resolveFixedRuntimeSearchBlockingReason([
      {
        search_round: 1,
        primary_keyword: '球墨铸铁井盖',
        entered_results_page: true,
        results_count: 0,
        results_path: '',
        round_failure_reason: '',
      },
    ] as never)).toBe('');
  });

  it('treats all browser target crashed rounds as fixed runtime blockers', () => {
    expect(resolveFixedRuntimeSearchBlockingReason([
      {
        search_round: 1,
        primary_keyword: '岩棉板 TR10 容重100kg',
        entered_results_page: false,
        results_path: '',
        round_failure_reason: 'browser_target_crashed',
      },
      {
        search_round: 2,
        primary_keyword: '岩棉板',
        entered_results_page: false,
        results_path: '',
        round_failure_reason: 'browser_target_crashed',
      },
    ] as never)).toBe('browser_target_crashed');
  });

  it('fails the fixed runtime item for browser target crashes instead of completing without candidates', () => {
    expect(shouldFailFixedRuntimeSearchBlockingReason('browser_target_crashed')).toBe(true);
    expect(shouldFailFixedRuntimeSearchBlockingReason('no_search_candidates')).toBe(false);
    expect(shouldFailFixedRuntimeSearchBlockingReason('captcha_intercept')).toBe(false);
  });

  it('builds fixed runtime llm context with normalized text fields', () => {
    expect(buildFixedRuntimeLlmContext({
      targetRow: {
        名称: '  地脚螺栓  ',
        品牌: '  哈光  ',
        规格型号: ' M16 x 200 ',
      },
      analysis: {
        brand_term: '  ',
        simplified_spec: '  M16   x 200  ',
        material: '  钢结构  ',
      },
      aiKeywordAnalysis: {
        suggested_keyword: '  地脚螺栓  ',
        structure_summary: '  需要匹配钢结构用地脚螺栓。  ',
        product_type: '  紧固件  ',
      },
    } as never, [
      {
        round: 1,
        label: '主搜索词',
        query: '地脚螺栓',
        status: 'success',
        failure_reason: '',
      },
    ])).toEqual({
      primary_search_term: '地脚螺栓',
      standardized_name: '地脚螺栓',
      brand: '哈光',
      standardized_spec: 'M16 x 200',
      category: '钢结构',
      ai_reasoning: '需要匹配钢结构用地脚螺栓。',
      product_type: '紧固件',
    });
  });

  it('falls back to extended best matches when image stage fails', async () => {
    const searchRunDir = await mkdtemp(path.join(os.tmpdir(), 'row-pipeline-sync-'));
    tempDirs.push(searchRunDir);

    const reportDir = path.join(searchRunDir, 'extended-best-matches');
    await mkdir(reportDir, { recursive: true });
    await writeFile(
      path.join(reportDir, 'extended-best-matches-report.json'),
      JSON.stringify({
        generated_at: new Date().toISOString(),
        records: [
          {
            title: '木胶板 2440*1220*15mm',
            offer_url: 'https://detail.1688.com/offer/test.html',
            image_url: 'https://img.example.com/test.jpg',
            merchant_name: '测试商家',
            merchant_url: 'https://shop.example.com/test',
            wangwang_url: 'https://wangwang.example.com/test',
            offer_screenshot_path: '/tmp/test.jpg',
            contact_page_url: 'https://shop.example.com/test/contact',
            detected_contacts: [{ type: 'mobile', value: '13800000000' }],
          },
        ],
      }),
      'utf8',
    );

    const outcome = await resolveImageStageOutcome({
      search: {
        searchRunDir,
        bestMatchImage: { selected_from_keyword: '木胶板 2440*1220*15mm' },
        validRound: null,
        initialRounds: [],
      },
      imageResult: null,
      limit: 5,
    });

    expect(outcome.source).toBe('extended_best_match_fallback');
    expect(outcome.mainKeyword).toBe('木胶板 2440*1220*15mm');
    expect(outcome.products).toHaveLength(1);
    expect(outcome.products[0]).toMatchObject({
      title: '木胶板 2440*1220*15mm',
      merchant_name: '测试商家',
    });
  });

  it('returns an empty extended-best-match fallback when the report is missing', async () => {
    const searchRunDir = await mkdtemp(path.join(os.tmpdir(), 'row-pipeline-sync-'));
    tempDirs.push(searchRunDir);

    const outcome = await resolveImageStageOutcome({
      search: {
        searchRunDir,
        bestMatchImage: { selected_from_keyword: '木胶板 2440*1220*15mm' },
        validRound: null,
        initialRounds: [],
      },
      imageResult: null,
      limit: 5,
    });

    expect(outcome.source).toBe('extended_best_match_fallback');
    expect(outcome.mainKeyword).toBe('木胶板 2440*1220*15mm');
    expect(outcome.products).toEqual([]);
  });
});

describe('resolveFinalResultProductTitle', () => {
  const generatedTitle = 'hO7TX2rRhkJDVqeFGucSXQ|aetU-Q|BUpWdg_0|By-uiQ|0';

  it('treats generated resource identifiers as unsafe product titles', () => {
    expect(isUnsafeProductTitle(generatedTitle)).toBe(true);
  });

  it('falls back to the candidate title-text before writing final_result_products', () => {
    expect(resolveFinalResultProductTitle({
      title: generatedTitle,
      'title-text': '1kV冷缩电缆终端头 120mm2 铝芯',
      offer_url: 'https://detail.1688.com/offer/123.html',
    })).toBe('1kV冷缩电缆终端头 120mm2 铝芯');
  });

  it('does not treat a sanitized generated resource identifier as a usable title', () => {
    expect(resolveFinalResultProductTitle({
      title: 'vZepVk9VbDxJ_LdQ9WgFhQ|aet_Kg|BUpWdg_0|By-uiQ|0',
      'title-text': '岩棉净化板湖南厂家直发阻燃防火保温车间隔断吊顶岩棉夹芯彩钢板',
      offer_url: 'https://detail.1688.com/offer/1028505114295.html',
    })).toBe('岩棉净化板湖南厂家直发阻燃防火保温车间隔断吊顶岩棉夹芯彩钢板');
  });

  it('falls back to search round results with the same offer_url when the stored title is dirty', () => {
    expect(resolveFinalResultProductTitle({
      title: generatedTitle,
      offer_url: 'https://detail.1688.com/offer/123.html?spm=a261y',
    }, [
      {
        title: '1kV冷缩电缆终端头 120mm2 铝芯',
        offer_url: 'https://detail.1688.com/offer/123.html',
      },
    ])).toBe('1kV冷缩电缆终端头 120mm2 铝芯');
  });
});

describe('dedupeFinalResultProductRowsByMerchant', () => {
  it('keeps one row per merchant before final_result_products insert', () => {
    const rows = dedupeFinalResultProductRowsByMerchant([
      {
        merchant_name: '商家 A',
        merchant_url: 'https://shop123.1688.com/?spm=a261y',
        offer_url: 'https://detail.1688.com/offer/1.html',
      },
      {
        merchant_name: '商家 A',
        merchant_url: 'http://shop123.1688.com/',
        offer_url: 'https://detail.1688.com/offer/2.html',
      },
      {
        merchant_name: '商家 B',
        merchant_url: 'https://shop456.1688.com',
        offer_url: 'https://detail.1688.com/offer/3.html',
      },
    ]);

    expect(rows.map((row) => row.offer_url)).toEqual([
      'https://detail.1688.com/offer/1.html',
      'https://detail.1688.com/offer/3.html',
    ]);
  });
});

describe('splitFixedRuntimeCandidatesByMatchBucket', () => {
  function candidate(overrides: Record<string, unknown>) {
    const merchantName = String(overrides.merchant_name ?? '商家');
    const merchantUrl = String(overrides.merchant_url ?? `https://${merchantName}.1688.com`);
    const offerUrl = String(overrides.url ?? `https://detail.1688.com/offer/${merchantName}.html`);
    return {
      input: '哈光 DQL-8 罗盘',
      'title-text': '哈光 DQL 候选',
      url: offerUrl,
      score: Number(overrides.score ?? 80),
      search_round: Number(overrides.search_round ?? 1),
      round_label: '主搜索词',
      source_product: {
        title: '哈光 DQL 候选',
        offer_url: offerUrl,
        merchant_name: merchantName,
        merchant_url: merchantUrl,
        image_url: '',
        wangwang_url: '',
        contact_page_url: '',
        offer_screenshot_path: '',
      },
      base_score: Number(overrides.score ?? 80),
      confidence: 0.8,
      is_match: overrides.match_bucket === 'matched',
      match_bucket: overrides.match_bucket,
      reason: '',
      judge_status: 'judged',
    };
  }

  it('keeps matched merchants ahead of possible duplicates', () => {
    const { matchedCandidates, possibleCandidates } = splitFixedRuntimeCandidatesByMatchBucket([
      candidate({ merchant_name: '哈光供应商', merchant_url: 'https://shop.1688.com/hg', match_bucket: 'possible', url: 'https://detail.1688.com/offer/possible.html', score: 99 }),
      candidate({ merchant_name: '哈光供应商', merchant_url: 'https://shop.1688.com/hg', match_bucket: 'matched', url: 'https://detail.1688.com/offer/matched.html', score: 80 }),
      candidate({ merchant_name: '备选供应商', merchant_url: 'https://shop.1688.com/backup', match_bucket: 'possible', url: 'https://detail.1688.com/offer/backup-a.html', score: 70 }),
      candidate({ merchant_name: '备选供应商', merchant_url: 'https://shop.1688.com/backup', match_bucket: 'possible', url: 'https://detail.1688.com/offer/backup-b.html', score: 60 }),
    ] as never);

    expect(matchedCandidates.map((item) => item.url)).toEqual(['https://detail.1688.com/offer/matched.html']);
    expect(possibleCandidates.map((item) => item.url)).toEqual(['https://detail.1688.com/offer/backup-a.html']);
  });
});

describe('buildFixedRuntimeDiagnosticsSummary', () => {
  it('keeps per-candidate failure reason summaries for fixed runtime shortages', () => {
    const summary = buildFixedRuntimeDiagnosticsSummary({
      materialId: 'material-1',
      batchId: 'CG-20260429-002',
      shortageReason: 'offer_detail_not_ready_but_contact_failed',
      targetQualifiedCount: 3,
      llmMatchedCount: 2,
      mergedCandidateCount: 5,
      qualifiedContactCount: 1,
      attemptedContactCount: 2,
      contactEnrichmentResult: {
        enriched: [],
        attemptedCount: 2,
        attemptedCandidateCount: 2,
        qualifiedCount: 1,
        duplicateMerchantCount: 0,
        targetQualifiedCount: 3,
        initialWindowSize: 2,
        shortageReason: 'offer_detail_not_ready_but_contact_failed',
        attempts: [
          {
            candidate_index: 1,
            phase: 'initial_window',
            input: '球墨铸铁井盖',
            'title-text': '井盖候选 A',
            url: 'https://detail.1688.com/offer/1.html',
            score: 98,
            is_match: true,
            reason: '匹配',
            search_round: 1,
            round_label: '主搜索词',
            screenshot_path: '',
            contact_page_url: 'https://shop1.1688.com/page/contactinfo.htm',
            contactPageUrl: 'https://shop1.1688.com/page/contactinfo.htm',
            detected_contacts: [],
            detail_contacts: [],
            contact_page_contacts: [],
            has_phone_contact: false,
            offerScreenshotStatus: 'detail_not_ready',
            contactFetchStatus: 'error',
            contactFetchMethod: 'direct_contact_url',
            wangwang_url: '',
            wangwang_original_url: '',
            wangwangFetchStatus: 'skipped',
            wangwangFetchMethod: '',
            wangwangFetchReason: '',
            qualification_status: 'rejected_contact_error',
            contact_collection_reason: 'offer_detail_not_ready_but_contact_failed',
            error_message: '',
            inserted_rank: null,
          },
          {
            candidate_index: 2,
            phase: 'initial_window',
            input: '球墨铸铁井盖',
            'title-text': '井盖候选 B',
            url: 'https://detail.1688.com/offer/2.html',
            score: 96,
            is_match: true,
            reason: '匹配',
            search_round: 1,
            round_label: '主搜索词',
            screenshot_path: '/tmp/offer-2.jpg',
            contact_page_url: 'https://shop2.1688.com/page/contactinfo.htm',
            contactPageUrl: 'https://shop2.1688.com/page/contactinfo.htm',
            detected_contacts: [{ type: 'mobile', value: '13800000002' }],
            detail_contacts: [],
            contact_page_contacts: [{ type: 'mobile', value: '13800000002' }],
            has_phone_contact: true,
            offerScreenshotStatus: 'ok',
            contactFetchStatus: 'ok',
            contactFetchMethod: 'direct_contact_url',
            wangwang_url: '',
            wangwang_original_url: '',
            wangwangFetchStatus: 'skipped',
            wangwangFetchMethod: '',
            wangwangFetchReason: '',
            qualification_status: 'qualified',
            contact_collection_reason: '',
            error_message: '',
            inserted_rank: 1,
          },
        ],
      },
      artifacts: {
        llmMatchResultsPath: '/tmp/llm-match-results.json',
        contactEnrichmentResultsPath: '/tmp/contact-enrichment-results.json',
        finalSelectedProductsPath: '/tmp/final-selected-products.json',
      },
    });

    expect(summary.reason_counts).toMatchObject({
      offer_detail_not_ready_but_contact_failed: 1,
      qualified: 1,
    });
    expect(summary.offer_screenshot_status_counts).toMatchObject({
      detail_not_ready: 1,
      ok: 1,
    });
    expect(summary.contact_fetch_status_counts).toMatchObject({
      error: 1,
      ok: 1,
    });
    expect(summary.candidate_attempts[0]).toMatchObject({
      candidate_index: 1,
      title: '井盖候选 A',
      failure_reason: 'offer_detail_not_ready_but_contact_failed',
      offerScreenshotStatus: 'detail_not_ready',
      contactFetchStatus: 'error',
    });
  });
});
