import { beforeEach, describe, expect, it, vi } from 'vitest';

import { deleteSearchStageGroup, fetchSearchResults, fetchSearchStageSummaries } from '@/services/searchService';
import { getSupabaseClient } from '@/lib/supabase';

vi.mock('@/lib/supabase', () => ({
  getSupabaseClient: vi.fn(),
}));

vi.mock('@/services/dataAccessScope', () => ({
  getVisibleBatchIds: vi.fn(async () => ['BATCH-001', 'BATCH-002', 'BATCH-003', 'BATCH-HIT', 'BATCH-FIXED', 'BATCH-RESULT-LEGACY', 'BATCH-LEGACY-SCHEMA', 'BATCH-LEGACY', 'BATCH-MULTI', 'BATCH-REPEAT', 'BATCH-FAIL']),
}));

type QueryResponse = {
  data: unknown[] | null;
  error: { code?: string; message: string } | null;
};

type QueryResponses = Record<string, QueryResponse | QueryResponse[]>;

function createQuery(response: QueryResponse) {
  const query = {
    order: vi.fn(() => query),
    eq: vi.fn(() => query),
    or: vi.fn(() => query),
    not: vi.fn(() => query),
    in: vi.fn(() => query),
    then: (onFulfilled: (value: QueryResponse) => unknown, onRejected?: (reason: unknown) => unknown) =>
      Promise.resolve(response).then(onFulfilled, onRejected),
    catch: (onRejected: (reason: unknown) => unknown) => Promise.resolve(response).catch(onRejected),
  };

  return query;
}

function createSupabaseMock(responses: QueryResponses) {
  const responseQueues = new Map(
    Object.entries(responses).map(([table, response]) => [
      table,
      {
        repeat: !Array.isArray(response),
        items: Array.isArray(response) ? [...response] : [response],
      },
    ]),
  );
  const nextResponse = (table: string) => {
    const queue = responseQueues.get(table);
    if (!queue) {
      return { data: [], error: null };
    }

    if (queue.repeat) {
      return queue.items[0];
    }

    return queue.items.shift() ?? { data: [], error: null };
  };

  return {
    from: vi.fn((table: string) => ({
      select: vi.fn(() => createQuery(nextResponse(table))),
      delete: vi.fn(() => createQuery(nextResponse(table))),
    })),
  };
}

describe('searchService', () => {
  beforeEach(() => {
    vi.resetAllMocks();
  });

  it('maps final_result_products with procurement and standardized lookups', async () => {
    vi.mocked(getSupabaseClient).mockReturnValue(createSupabaseMock({
      final_result_products: {
        data: [{
          id: 'fr-001',
          task_id: 'BATCH-001-ss-001',
          batch_id: 'BATCH-001',
          material_id: 'mat-001',
          main_keyword: '球墨铸铁井盖',
          stage_order: 1,
          stage_label: '首轮搜索',
          stage_kind: 'keyword',
          stage_main_keyword: '球墨铸铁井盖',
          stage_keyword_hit_rate: 0.75,
          is_placeholder: false,
          placeholder_reason: '',
          rank: 1,
          title: '球墨铸铁井盖重型',
          offer_url: 'https://detail.1688.com/offer/1.html',
          image_url: 'https://img.example.com/1.jpg',
          merchant_name: '有效商家',
          merchant_url: 'https://shop.1688.com/valid',
          wangwang_url: 'https://wangwang.example.com/1',
          offer_screenshot_url: 'https://img.example.com/1.png',
          contact_page_url: 'https://shop.1688.com/valid/contact',
          detected_contacts: [{ type: 'mobile', value: '17736998988' }],
          score: 75,
          is_match: true,
          match_bucket: 'matched',
          reason: '标题包含井盖重型和采购主词，语义一致。',
          created_at: '2026-04-15T11:00:00.000Z',
          finished_at: '2026-04-15T11:00:00.000Z',
        }],
        error: null,
      },
      procurement_materials: {
        data: [{ id: 'mat-001', batch_id: 'BATCH-001', material_name: '球墨铸铁井盖' }],
        error: null,
      },
      standardized_results: {
        data: [{
          task_id: 'BATCH-001-ss-001',
          batch_id: 'BATCH-001',
          material_id: 'mat-001',
          standardized_name: '球墨铸铁井盖重型',
          primary_search_term: '球墨铸铁井盖',
        }],
        error: null,
      },
      search_rounds: {
        data: [{
          batch_id: 'BATCH-001',
          material_id: 'mat-001',
          stage_order: 1,
          stage_label: '首轮搜索',
          stage_kind: 'keyword',
          stage_main_keyword: '球墨铸铁井盖',
          keyword_hit_rate: 0.75,
          is_valid_search: true,
          stop_reason: '',
          contact_enrichment_skip_reason: '',
          created_at: '2026-04-15T10:59:00.000Z',
        }],
        error: null,
      },
    }) as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(fetchSearchResults()).resolves.toEqual([
      expect.objectContaining({
        id: 'fr-001',
        task_id: 'BATCH-001-ss-001',
        material_id: 'mat-001',
        material_name: '球墨铸铁井盖',
        standardized_name: '球墨铸铁井盖重型',
        mobile: '17736998988',
        score: 75,
        is_match: true,
        match_bucket: 'matched',
        reason: '标题包含井盖重型和采购主词，语义一致。',
        keyword_hit_rate: 0.75,
      }),
    ]);
  });

  it('picks the latest valid stage summary per batch material stage', async () => {
    vi.mocked(getSupabaseClient).mockReturnValue(createSupabaseMock({
      search_rounds: {
        data: [
          {
            task_id: 'BATCH-002-ss-001',
            batch_id: 'BATCH-002',
            material_id: 'mat-002',
            stage_order: 1,
            stage_label: '首轮搜索',
            stage_kind: 'keyword',
            stage_main_keyword: '热镀锌钢管',
            keyword_hit_rate: 0.25,
            is_valid_search: false,
            stop_reason: '',
            contact_enrichment_skip_reason: '',
            created_at: '2026-04-15T10:00:00.000Z',
          },
          {
            task_id: 'BATCH-002-ss-001',
            batch_id: 'BATCH-002',
            material_id: 'mat-002',
            stage_order: 1,
            stage_label: '首轮搜索',
            stage_kind: 'keyword',
            stage_main_keyword: '热镀锌钢管',
            keyword_hit_rate: 0.5,
            is_valid_search: true,
            stop_reason: '',
            contact_enrichment_skip_reason: '',
            created_at: '2026-04-15T10:05:00.000Z',
          },
        ],
        error: null,
      },
    }) as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(fetchSearchStageSummaries()).resolves.toEqual([
      expect.objectContaining({
        batch_id: 'BATCH-002',
        task_id: 'BATCH-002-ss-001',
        material_id: 'mat-002',
        stage_order: 1,
        keyword_hit_rate: 0.5,
      }),
    ]);
  });

  it('falls back to legacy search_rounds columns when stage timing columns are missing for summaries', async () => {
    vi.mocked(getSupabaseClient).mockReturnValue(createSupabaseMock({
      search_rounds: [
        {
          data: null,
          error: {
            code: '42703',
            message: 'column search_rounds.stage_started_at does not exist',
          },
        },
        {
          data: [{
            task_id: 'BATCH-LEGACY-SCHEMA-ss-001',
            batch_id: 'BATCH-LEGACY-SCHEMA',
            material_id: 'mat-legacy-schema',
            stage_order: 1,
            stage_label: '首轮搜索',
            stage_kind: 'keyword',
            stage_main_keyword: '桥架',
            keyword_hit_rate: 0.5,
            is_valid_search: true,
            stop_reason: '',
            contact_enrichment_skip_reason: '',
            created_at: '2026-04-28T08:00:00.000Z',
          }],
          error: null,
        },
      ],
    }) as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(fetchSearchStageSummaries()).resolves.toEqual([
      expect.objectContaining({
        task_id: 'BATCH-LEGACY-SCHEMA-ss-001',
        batch_id: 'BATCH-LEGACY-SCHEMA',
        material_id: 'mat-legacy-schema',
        stage_started_at: null,
        stage_duration_ms: null,
      }),
    ]);
  });

  it('uses persisted stage hit rate without recalculating from result scores', async () => {
    vi.mocked(getSupabaseClient).mockReturnValue(createSupabaseMock({
      search_rounds: {
        data: [
          {
            task_id: 'BATCH-HIT-ss-001',
            batch_id: 'BATCH-HIT',
            material_id: 'mat-hit',
            search_round: 1,
            stage_order: 1,
            stage_label: '关键词搜索',
            stage_kind: 'keyword',
            stage_main_keyword: '电磁阀',
            keyword_hit_rate: 0,
            results: [{ title: '气动电磁阀', score: 100 }],
            is_valid_search: true,
            stop_reason: '',
            contact_enrichment_skip_reason: '',
            created_at: '2026-04-24T08:00:00.000Z',
          },
          {
            task_id: 'BATCH-HIT-ss-001',
            batch_id: 'BATCH-HIT',
            material_id: 'mat-hit',
            search_round: 2,
            stage_order: 1,
            stage_label: '关键词搜索',
            stage_kind: 'keyword',
            stage_main_keyword: '电磁阀',
            keyword_hit_rate: 0,
            results: [{ title: '不相关商品', score: 0 }],
            is_valid_search: true,
            stop_reason: '',
            contact_enrichment_skip_reason: '',
            created_at: '2026-04-24T08:01:00.000Z',
          },
          {
            task_id: 'BATCH-HIT-ss-001',
            batch_id: 'BATCH-HIT',
            material_id: 'mat-hit',
            search_round: 3,
            stage_order: 1,
            stage_label: '关键词搜索',
            stage_kind: 'keyword',
            stage_main_keyword: '电磁阀',
            keyword_hit_rate: 0,
            results: [{ title: '重复轮次商品', score: 100 }],
            is_valid_search: false,
            stop_reason: 'duplicate_skip',
            contact_enrichment_skip_reason: '',
            created_at: '2026-04-24T08:02:00.000Z',
          },
        ],
        error: null,
      },
    }) as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(fetchSearchStageSummaries()).resolves.toEqual([
      expect.objectContaining({
        batch_id: 'BATCH-HIT',
        material_id: 'mat-hit',
        keyword_hit_rate: 0,
      }),
    ]);
  });

  it('preserves persisted stage hit rate when rounds cannot be reliably recalculated', async () => {
    vi.mocked(getSupabaseClient).mockReturnValue(createSupabaseMock({
      search_rounds: {
        data: [
          {
            task_id: 'BATCH-FIXED-ss-001',
            batch_id: 'BATCH-FIXED',
            material_id: 'mat-fixed',
            search_round: 1,
            stage_order: 1,
            stage_label: '关键词搜索',
            stage_kind: 'keyword',
            stage_main_keyword: '岩棉板',
            keyword_hit_rate: 1,
            keyword_hit_rate_source: 'fixed_runtime',
            results: [{ title: '岩棉板保温板' }],
            is_valid_search: false,
            stop_reason: '',
            contact_enrichment_skip_reason: '',
            created_at: '2026-04-24T08:00:00.000Z',
          },
          {
            task_id: 'BATCH-FIXED-ss-001',
            batch_id: 'BATCH-FIXED',
            material_id: 'mat-fixed',
            search_round: 2,
            stage_order: 1,
            stage_label: '关键词搜索',
            stage_kind: 'keyword',
            stage_main_keyword: '岩棉板',
            keyword_hit_rate: 1,
            keyword_hit_rate_source: 'fixed_runtime',
            results: [{ title: '防火岩棉彩钢板' }],
            is_valid_search: false,
            stop_reason: '',
            contact_enrichment_skip_reason: '',
            created_at: '2026-04-24T08:01:00.000Z',
          },
        ],
        error: null,
      },
    }) as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(fetchSearchStageSummaries()).resolves.toEqual([
      expect.objectContaining({
        batch_id: 'BATCH-FIXED',
        material_id: 'mat-fixed',
        keyword_hit_rate: 1,
      }),
    ]);
  });

  it('hides rejected no-phone merchants by default', async () => {
    vi.mocked(getSupabaseClient).mockReturnValue(createSupabaseMock({
      final_result_products: {
        data: [{
          id: 'fr-002',
          task_id: 'BATCH-003-ss-001',
          batch_id: 'BATCH-003',
          material_id: 'mat-003',
          main_keyword: '电线',
          stage_order: 1,
          stage_label: '首轮搜索',
          stage_kind: 'keyword',
          stage_main_keyword: '电线',
          stage_keyword_hit_rate: 0.4,
          is_placeholder: false,
          placeholder_reason: '',
          rank: 1,
          title: '无号码商家商品',
          offer_url: 'https://detail.1688.com/offer/2.html',
          image_url: 'https://img.example.com/2.jpg',
          merchant_name: '无号码商家',
          merchant_url: 'https://shop.1688.com/no-phone',
          wangwang_url: '',
          offer_screenshot_url: '',
          contact_page_url: '',
          detected_contacts: [],
          has_phone_contact: false,
          qualification_status: 'rejected_no_phone',
          contact_collection_reason: 'contact_navigation_failed',
          created_at: '2026-04-15T11:00:00.000Z',
          finished_at: '2026-04-15T11:00:00.000Z',
        }],
        error: null,
      },
      procurement_materials: {
        data: [{ id: 'mat-003', batch_id: 'BATCH-003', material_name: '电线' }],
        error: null,
      },
      standardized_results: {
        data: [{
          batch_id: 'BATCH-003',
          material_id: 'mat-003',
          standardized_name: '电线',
          primary_search_term: '电线',
        }],
        error: null,
      },
      search_rounds: {
        data: [],
        error: null,
      },
    }) as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(fetchSearchResults()).resolves.toEqual([]);
    await expect(fetchSearchResults({ includeRejectedNoPhone: true })).resolves.toEqual([
      expect.objectContaining({
        id: 'fr-002',
        task_id: 'BATCH-003-ss-001',
        qualification_status: 'rejected_no_phone',
      }),
    ]);
  });

  it('preserves possible match buckets from final_result_products', async () => {
    vi.mocked(getSupabaseClient).mockReturnValue(createSupabaseMock({
      final_result_products: {
        data: [{
          id: 'fr-possible',
          task_id: 'BATCH-003-ss-002',
          batch_id: 'BATCH-003',
          material_id: 'mat-003',
          main_keyword: '地质罗盘',
          stage_order: 1,
          stage_label: '关键词搜索',
          stage_kind: 'keyword',
          stage_main_keyword: '地质罗盘',
          stage_keyword_hit_rate: 0.4,
          is_placeholder: false,
          placeholder_reason: '',
          rank: 2,
          title: 'DQY-1地质罗盘仪',
          offer_url: 'https://detail.1688.com/offer/possible.html',
          image_url: '',
          merchant_name: '可能商家',
          merchant_url: 'https://shop.1688.com/possible',
          wangwang_url: '',
          offer_screenshot_url: '',
          contact_page_url: '',
          detected_contacts: [{ type: 'mobile', value: '13800138000' }],
          is_match: false,
          match_bucket: 'possible',
          reason: '同品类但型号未精确命中。',
          qualification_status: 'qualified',
          created_at: '2026-04-15T11:00:00.000Z',
          finished_at: '2026-04-15T11:00:00.000Z',
        }],
        error: null,
      },
      procurement_materials: {
        data: [{ id: 'mat-003', batch_id: 'BATCH-003', material_name: '哈光 DQL-8A 地质罗盘' }],
        error: null,
      },
      standardized_results: {
        data: [{
          batch_id: 'BATCH-003',
          material_id: 'mat-003',
          standardized_name: '哈光 DQL-8A 地质罗盘',
          primary_search_term: '地质罗盘',
        }],
        error: null,
      },
      search_rounds: {
        data: [],
        error: null,
      },
      search_task_items: {
        data: [],
        error: null,
      },
    }) as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(fetchSearchResults()).resolves.toEqual([
      expect.objectContaining({
        id: 'fr-possible',
        is_match: false,
        match_bucket: 'possible',
        reason: '同品类但型号未精确命中。',
      }),
    ]);
  });

  it('falls back to legacy search_rounds columns when stage timing columns are missing for result hit rates', async () => {
    vi.mocked(getSupabaseClient).mockReturnValue(createSupabaseMock({
      final_result_products: {
        data: [{
          id: 'fr-legacy-schema',
          task_id: 'BATCH-RESULT-LEGACY-ss-001',
          batch_id: 'BATCH-RESULT-LEGACY',
          material_id: 'mat-result-legacy',
          main_keyword: '水泵',
          stage_order: 1,
          stage_label: '首轮搜索',
          stage_kind: 'keyword',
          stage_main_keyword: '水泵',
          stage_keyword_hit_rate: null,
          is_placeholder: false,
          placeholder_reason: '',
          rank: 1,
          title: '水泵供应商',
          offer_url: 'https://detail.1688.com/offer/pump.html',
          image_url: '',
          merchant_name: '水泵商家',
          merchant_url: '',
          wangwang_url: '',
          offer_screenshot_url: '',
          contact_page_url: '',
          detected_contacts: [{ type: 'mobile', value: '13800138000' }],
          created_at: '2026-04-28T09:00:00.000Z',
          finished_at: '2026-04-28T09:00:00.000Z',
        }],
        error: null,
      },
      procurement_materials: {
        data: [{ id: 'mat-result-legacy', batch_id: 'BATCH-RESULT-LEGACY', material_name: '循环水泵' }],
        error: null,
      },
      standardized_results: {
        data: [{
          batch_id: 'BATCH-RESULT-LEGACY',
          material_id: 'mat-result-legacy',
          standardized_name: '循环水泵',
          primary_search_term: '水泵',
        }],
        error: null,
      },
      search_rounds: [
        {
          data: null,
          error: {
            code: '42703',
            message: 'column search_rounds.stage_started_at does not exist',
          },
        },
        {
          data: [{
            task_id: 'BATCH-RESULT-LEGACY-ss-001',
            batch_id: 'BATCH-RESULT-LEGACY',
            material_id: 'mat-result-legacy',
            stage_order: 1,
            stage_label: '首轮搜索',
            stage_kind: 'keyword',
            stage_main_keyword: '水泵',
            keyword_hit_rate: 0.8,
            is_valid_search: true,
            stop_reason: '',
            contact_enrichment_skip_reason: '',
            created_at: '2026-04-28T08:59:00.000Z',
          }],
          error: null,
        },
      ],
    }) as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(fetchSearchResults()).resolves.toEqual([
      expect.objectContaining({
        id: 'fr-legacy-schema',
        keyword_hit_rate: 0.8,
      }),
    ]);
  });

  it('keeps repeated tasks for the same batch material separate', async () => {
    vi.mocked(getSupabaseClient).mockReturnValue(createSupabaseMock({
      final_result_products: {
        data: [
          {
            id: 'fr-task-002',
            task_id: 'BATCH-REPEAT-ss-002',
            batch_id: 'BATCH-REPEAT',
            material_id: 'mat-repeat',
            main_keyword: '轴承',
            stage_order: 1,
            stage_label: '关键词搜索',
            stage_kind: 'keyword',
            stage_main_keyword: '轴承',
            stage_keyword_hit_rate: null,
            is_placeholder: false,
            placeholder_reason: '',
            rank: 1,
            title: '第二个任务商家',
            offer_url: 'https://detail.1688.com/offer/repeat-2.html',
            image_url: '',
            merchant_name: '第二个任务商家',
            merchant_url: '',
            wangwang_url: '',
            offer_screenshot_url: 'https://storage.example.com/search-task-BATCH-REPEAT-ss-002/material-mat-repeat/offer-screenshots/stage-1-rank-1.jpg',
            contact_page_url: '',
            detected_contacts: [{ type: 'mobile', value: '13800138002' }],
            created_at: '2026-05-05T02:00:00.000Z',
            finished_at: '2026-05-05T02:00:00.000Z',
          },
          {
            id: 'fr-task-001',
            task_id: 'BATCH-REPEAT-ss-001',
            batch_id: 'BATCH-REPEAT',
            material_id: 'mat-repeat',
            main_keyword: '轴承',
            stage_order: 1,
            stage_label: '关键词搜索',
            stage_kind: 'keyword',
            stage_main_keyword: '轴承',
            stage_keyword_hit_rate: null,
            is_placeholder: false,
            placeholder_reason: '',
            rank: 1,
            title: '第一个任务商家',
            offer_url: 'https://detail.1688.com/offer/repeat-1.html',
            image_url: '',
            merchant_name: '第一个任务商家',
            merchant_url: '',
            wangwang_url: '',
            offer_screenshot_url: 'https://storage.example.com/search-task-BATCH-REPEAT-ss-001/material-mat-repeat/offer-screenshots/stage-1-rank-1.jpg',
            contact_page_url: '',
            detected_contacts: [{ type: 'mobile', value: '13800138001' }],
            created_at: '2026-05-05T01:00:00.000Z',
            finished_at: '2026-05-05T01:00:00.000Z',
          },
        ],
        error: null,
      },
      procurement_materials: {
        data: [{ id: 'mat-repeat', batch_id: 'BATCH-REPEAT', material_name: '轴承' }],
        error: null,
      },
      standardized_results: {
        data: [{ batch_id: 'BATCH-REPEAT', material_id: 'mat-repeat', standardized_name: '深沟球轴承', primary_search_term: '轴承' }],
        error: null,
      },
      search_rounds: {
        data: [
          {
            task_id: 'BATCH-REPEAT-ss-001',
            batch_id: 'BATCH-REPEAT',
            material_id: 'mat-repeat',
            stage_order: 1,
            stage_label: '关键词搜索',
            stage_kind: 'keyword',
            stage_main_keyword: '轴承',
            keyword_hit_rate: 0.25,
            is_valid_search: true,
            stop_reason: '',
            contact_enrichment_skip_reason: '',
            created_at: '2026-05-05T00:59:00.000Z',
          },
          {
            task_id: 'BATCH-REPEAT-ss-002',
            batch_id: 'BATCH-REPEAT',
            material_id: 'mat-repeat',
            stage_order: 1,
            stage_label: '关键词搜索',
            stage_kind: 'keyword',
            stage_main_keyword: '轴承',
            keyword_hit_rate: 0.8,
            is_valid_search: true,
            stop_reason: '',
            contact_enrichment_skip_reason: '',
            created_at: '2026-05-05T01:59:00.000Z',
          },
        ],
        error: null,
      },
      search_task_items: {
        data: [],
        error: null,
      },
    }) as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(fetchSearchResults({ includeRejectedNoPhone: true })).resolves.toEqual([
      expect.objectContaining({
        id: 'fr-task-002',
        task_id: 'BATCH-REPEAT-ss-002',
        keyword_hit_rate: 0.8,
        screenshot_public_url: 'https://storage.example.com/search-task-BATCH-REPEAT-ss-002/material-mat-repeat/offer-screenshots/stage-1-rank-1.jpg',
      }),
      expect.objectContaining({
        id: 'fr-task-001',
        task_id: 'BATCH-REPEAT-ss-001',
        keyword_hit_rate: 0.25,
        screenshot_public_url: 'https://storage.example.com/search-task-BATCH-REPEAT-ss-001/material-mat-repeat/offer-screenshots/stage-1-rank-1.jpg',
      }),
    ]);
  });

  it('keeps failure stage summaries scoped to the failed task', async () => {
    vi.mocked(getSupabaseClient).mockReturnValue(createSupabaseMock({
      search_rounds: {
        data: [
          {
            task_id: 'BATCH-FAIL-ss-001',
            batch_id: 'BATCH-FAIL',
            material_id: 'mat-fail',
            stage_order: 1,
            stage_label: '关键词搜索',
            stage_kind: 'keyword',
            stage_main_keyword: '风亭围栏',
            keyword_hit_rate: 0.67,
            is_valid_search: true,
            stop_reason: '',
            contact_enrichment_skip_reason: '',
            created_at: '2026-05-05T01:00:00.000Z',
          },
          {
            task_id: 'BATCH-FAIL-ss-002',
            batch_id: 'BATCH-FAIL',
            material_id: 'mat-fail',
            stage_order: 1,
            stage_label: '关键词搜索',
            stage_kind: 'keyword',
            stage_main_keyword: '风亭围栏',
            keyword_hit_rate: null,
            is_valid_search: false,
            stop_reason: 'browser_target_crashed',
            contact_enrichment_skip_reason: 'browser_target_crashed',
            created_at: '2026-05-05T02:00:00.000Z',
          },
        ],
        error: null,
      },
      search_task_items: {
        data: [],
        error: null,
      },
    }) as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(fetchSearchStageSummaries()).resolves.toEqual([
      expect.objectContaining({
        id: 'BATCH-FAIL::mat-fail::1::BATCH-FAIL-ss-001',
        task_id: 'BATCH-FAIL-ss-001',
        keyword_hit_rate: 0.67,
        stop_reason: '',
      }),
      expect.objectContaining({
        id: 'BATCH-FAIL::mat-fail::1::BATCH-FAIL-ss-002',
        task_id: 'BATCH-FAIL-ss-002',
        keyword_hit_rate: null,
        stop_reason: 'browser_target_crashed',
      }),
    ]);
  });

  it('deletes stage groups with task_id in every search artifact query', async () => {
    const eqCalls: Array<{ table: string; column: string; value: unknown }> = [];
    const createTrackedQuery = (table: string, response: QueryResponse) => {
      const query = {
        eq: vi.fn((column: string, value: unknown) => {
          eqCalls.push({ table, column, value });
          return query;
        }),
        in: vi.fn(() => query),
        then: (onFulfilled: (value: QueryResponse) => unknown, onRejected?: (reason: unknown) => unknown) =>
          Promise.resolve(response).then(onFulfilled, onRejected),
        catch: (onRejected: (reason: unknown) => unknown) => Promise.resolve(response).catch(onRejected),
      };
      return query;
    };
    const supabaseMock = {
      from: vi.fn((table: string) => ({
        select: vi.fn(() => createTrackedQuery(table, { data: [], error: null })),
        delete: vi.fn(() => createTrackedQuery(table, { data: [], error: null })),
      })),
    };
    vi.mocked(getSupabaseClient).mockReturnValue(supabaseMock as unknown as ReturnType<typeof getSupabaseClient>);

    await deleteSearchStageGroup({
      taskId: 'BATCH-REPEAT-ss-002',
      batchId: 'BATCH-REPEAT',
      materialId: 'mat-repeat',
      stageOrder: 1,
    });

    expect(eqCalls.filter((call) => call.column === 'task_id')).toEqual([
      { table: 'final_result_products', column: 'task_id', value: 'BATCH-REPEAT-ss-002' },
      { table: 'final_result_products', column: 'task_id', value: 'BATCH-REPEAT-ss-002' },
      { table: 'search_rounds', column: 'task_id', value: 'BATCH-REPEAT-ss-002' },
    ]);
  });

  it('backfills missing task_id for legacy search results from search_task_items timeline', async () => {
    vi.mocked(getSupabaseClient).mockReturnValue(createSupabaseMock({
      final_result_products: {
        data: [{
          id: 'legacy-fr-001',
          task_id: null,
          batch_id: 'BATCH-LEGACY',
          material_id: 'mat-legacy',
          main_keyword: '罗盘',
          stage_order: 1,
          stage_label: '首轮搜索',
          stage_kind: 'keyword',
          stage_main_keyword: '罗盘',
          stage_keyword_hit_rate: null,
          is_placeholder: false,
          placeholder_reason: '',
          rank: 1,
          title: '老数据结果',
          offer_url: 'https://detail.1688.com/offer/legacy.html',
          image_url: 'https://img.example.com/legacy.jpg',
          merchant_name: '老数据商家',
          merchant_url: 'https://shop.1688.com/legacy',
          wangwang_url: '',
          offer_screenshot_url: '',
          contact_page_url: '',
          detected_contacts: [{ type: 'mobile', value: '13800138000' }],
          created_at: '2026-04-22T08:09:19.000Z',
          finished_at: '2026-04-22T08:09:19.000Z',
        }],
        error: null,
      },
      procurement_materials: {
        data: [{ id: 'mat-legacy', batch_id: 'BATCH-LEGACY', material_name: '地质罗盘' }],
        error: null,
      },
      standardized_results: {
        data: [{
          batch_id: 'BATCH-LEGACY',
          material_id: 'mat-legacy',
          standardized_name: '地质罗盘',
          primary_search_term: '罗盘',
        }],
        error: null,
      },
      search_rounds: {
        data: [{
          task_id: null,
          batch_id: 'BATCH-LEGACY',
          material_id: 'mat-legacy',
          stage_order: 1,
          stage_label: '首轮搜索',
          stage_kind: 'keyword',
          stage_main_keyword: '罗盘',
          keyword_hit_rate: 0.6,
          is_valid_search: true,
          stop_reason: '',
          contact_enrichment_skip_reason: '',
          created_at: '2026-04-22T08:08:00.000Z',
        }],
        error: null,
      },
      search_task_items: {
        data: [
          {
            task_id: 'BATCH-LEGACY-ss-003',
            batch_id: 'BATCH-LEGACY',
            material_id: 'mat-legacy',
            created_at: '2026-04-22T08:00:08.000Z',
          },
          {
            task_id: 'BATCH-LEGACY-ss-002',
            batch_id: 'BATCH-LEGACY',
            material_id: 'mat-legacy',
            created_at: '2026-04-22T07:55:31.000Z',
          },
        ],
        error: null,
      },
    }) as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(fetchSearchResults()).resolves.toEqual([
      expect.objectContaining({
        id: 'legacy-fr-001',
        task_id: 'BATCH-LEGACY-ss-003',
        keyword_hit_rate: 0.6,
      }),
    ]);
  });

  it('keeps stage summaries from different inferred tasks separate', async () => {
    vi.mocked(getSupabaseClient).mockReturnValue(createSupabaseMock({
      search_rounds: {
        data: [
          {
            task_id: null,
            batch_id: 'BATCH-MULTI',
            material_id: 'mat-multi',
            stage_order: 1,
            stage_label: '首轮搜索',
            stage_kind: 'keyword',
            stage_main_keyword: '轴承',
            keyword_hit_rate: 0.35,
            is_valid_search: true,
            stop_reason: '',
            contact_enrichment_skip_reason: '',
            created_at: '2026-04-22T07:05:00.000Z',
          },
          {
            task_id: null,
            batch_id: 'BATCH-MULTI',
            material_id: 'mat-multi',
            stage_order: 1,
            stage_label: '首轮搜索',
            stage_kind: 'keyword',
            stage_main_keyword: '轴承',
            keyword_hit_rate: 0.72,
            is_valid_search: true,
            stop_reason: '',
            contact_enrichment_skip_reason: '',
            created_at: '2026-04-22T08:05:00.000Z',
          },
        ],
        error: null,
      },
      search_task_items: {
        data: [
          {
            task_id: 'BATCH-MULTI-ss-001',
            batch_id: 'BATCH-MULTI',
            material_id: 'mat-multi',
            created_at: '2026-04-22T07:00:00.000Z',
          },
          {
            task_id: 'BATCH-MULTI-ss-002',
            batch_id: 'BATCH-MULTI',
            material_id: 'mat-multi',
            created_at: '2026-04-22T08:00:00.000Z',
          },
        ],
        error: null,
      },
    }) as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(fetchSearchStageSummaries()).resolves.toEqual([
      expect.objectContaining({
        batch_id: 'BATCH-MULTI',
        task_id: 'BATCH-MULTI-ss-001',
        material_id: 'mat-multi',
        stage_order: 1,
        keyword_hit_rate: 0.35,
      }),
      expect.objectContaining({
        batch_id: 'BATCH-MULTI',
        task_id: 'BATCH-MULTI-ss-002',
        material_id: 'mat-multi',
        stage_order: 1,
        keyword_hit_rate: 0.72,
      }),
    ]);
  });
});
