// @vitest-environment jsdom
import '../../../test/dom-setup';
import { describe, it, expect, vi, beforeAll } from 'vitest';
import { render, screen } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

// Initialize i18next with minimal translations for this test.
// The component uses the 'detail' namespace.
beforeAll(async () => {
  if (!i18n.isInitialized) {
    await i18n.use(initReactI18next).init({
      lng: 'en',
      fallbackLng: 'en',
      resources: {
        en: {
          detail: {
            'delegateRuns.eventsEmpty': 'No events',
            'delegateRuns.subtaskGroupTitle': 'Subtask #{{n}}',
            'delegateRuns.subtaskSectionHeading': 'Delegate runs in subtasks',
            'delegateRuns.result': 'Result',
            'delegateRuns.abortReason': 'Abort reason',
            'delegateRuns.noDescription': '(no description)',
            'delegateRuns.toolCount': '{{count}} tools',
            'delegateRuns.childCount': '{{total}} children',
            'delegateRuns.childCountFailed': '{{total}} children, {{failed}} failed',
            'delegateRuns.moreEvents': '{{count}} more events (see the Trace tab for the full list)',
            'delegateRuns.toolSummary': 'Tools used',
            'delegateRuns.filesChanged': 'Changed files',
            'delegateRuns.eventsToggle': 'Detailed events ({{count}})',
            'subtasks.delegateSection': 'Delegated runs',
            'subtasks.delegateStatus.success': 'Done',
            'subtasks.delegateStatus.aborted': 'Aborted',
            'subtasks.delegateStatus.running': 'Running',
            'subtasks.delegateRunningTool': '{{tool}} running',
          },
          common: { loading: 'Loading...' },
        },
      },
      defaultNS: 'common',
      interpolation: { escapeValue: false },
    });
  }
});

vi.mock('../../../api', () => ({
  fetchDelegateRuns: vi.fn().mockResolvedValue({
    runs: [
      {
        delegateRunId: 'p1',
        parentRunId: null,
        description: '親委譲',
        depth: 1,
        status: 'success',
        startTs: '2026-01-01T00:00:00Z',
        endTs: '2026-01-01T00:01:00Z',
        eventCount: 2,
        toolCalls: 1,
        resultPreview: 'Success result preview',
        totalTokens: 1234,
        toolSummary: [{ tool: 'Write', count: 2 }, { tool: 'Read', count: 1 }],
        filesChanged: ['output/result.md', 'output/summary.txt'],
      },
      {
        delegateRunId: 'c1',
        parentRunId: 'p1',
        description: '子委譲1',
        depth: 2,
        status: 'success',
        startTs: '2026-01-01T00:00:30Z',
        endTs: '2026-01-01T00:00:45Z',
        eventCount: 1,
        toolCalls: 0,
        resultPreview: null,
        totalTokens: 0,
      },
      {
        delegateRunId: 'c2',
        parentRunId: 'p1',
        description: '子委譲2',
        depth: 2,
        status: 'success',
        startTs: '2026-01-01T00:00:46Z',
        endTs: '2026-01-01T00:01:00Z',
        eventCount: 1,
        toolCalls: 0,
        resultPreview: null,
        totalTokens: 567,
      },
      {
        delegateRunId: 'p2',
        parentRunId: null,
        description: '失敗した親委譲',
        depth: 1,
        status: 'success',
        startTs: '2026-01-01T00:02:00Z',
        endTs: '2026-01-01T00:03:00Z',
        eventCount: 1,
        toolCalls: 0,
        resultPreview: 'Operation completed with errors',
        totalTokens: 0,
      },
      {
        delegateRunId: 'c3',
        parentRunId: 'p2',
        description: '失敗した子委譲',
        depth: 2,
        status: 'aborted',
        startTs: '2026-01-01T00:02:30Z',
        endTs: '2026-01-01T00:03:00Z',
        eventCount: 1,
        toolCalls: 0,
        resultPreview: 'Failed',
        totalTokens: 100,
      },
      {
        delegateRunId: 'p3',
        parentRunId: null,
        description: '結果なし委譲',
        depth: 1,
        status: 'success',
        startTs: '2026-01-01T00:04:00Z',
        endTs: '2026-01-01T00:05:00Z',
        eventCount: 1,
        toolCalls: 0,
        resultPreview: null,
        totalTokens: 0,
      },
      {
        delegateRunId: 'p4',
        parentRunId: null,
        description: '実行中の親委譲',
        depth: 1,
        status: 'running',
        startTs: '2026-01-01T00:06:00Z',
        endTs: null,
        eventCount: 1,
        toolCalls: 0,
        resultPreview: null,
        totalTokens: 2000,
      },
      {
        delegateRunId: 'c4',
        parentRunId: 'p4',
        description: '実行中の子委譲',
        depth: 2,
        status: 'running',
        startTs: '2026-01-01T00:06:30Z',
        endTs: null,
        eventCount: 1,
        toolCalls: 0,
        resultPreview: null,
        totalTokens: 500,
      },
    ],
    subtasks: [
      {
        jobId: 'sub1',
        issueNumber: 1,
        depth: 1,
        status: 'succeeded',
        runs: [
          {
            delegateRunId: 's1',
            parentRunId: null,
            description: 'サブ委譲',
            depth: 1,
            status: 'success',
            startTs: '2026-01-01T00:00:00Z',
            endTs: '2026-01-01T00:01:00Z',
            eventCount: 1,
            toolCalls: 0,
            resultPreview: null,
            totalTokens: 3456,
          },
        ],
      },
    ],
  }),
  fetchDelegateRunTimeline: vi.fn(),
}));

import { DelegateRunsSection } from './DelegateRunsSection';
import * as api from '../../../api';
import userEvent from '@testing-library/user-event';

describe('DelegateRunsSection', () => {
  it('親委譲とサブタスクグループの両方を描画する', async () => {
    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
    render(
      <QueryClientProvider client={qc}>
        <DelegateRunsSection taskId={1} />
      </QueryClientProvider>,
    );
    // すべてのカードが描画される
    const buttons = await screen.findAllByRole('button');
    expect(buttons.length).toBeGreaterThan(0);
    // 親 run の description が表示される
    expect(screen.getByText('親委譲')).toBeInTheDocument();
    // サブタスク run の description が表示される
    expect(screen.getByText('サブ委譲')).toBeInTheDocument();
    // サブタスクグループ見出しが表示される（en or ja）
    expect(
      screen.getByText(/Subtask #1|サブタスク #1/),
    ).toBeInTheDocument();
  });

  it('子の件数バッジが閉じた親カードに表示される（2 子ケース）', async () => {
    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
    render(
      <QueryClientProvider client={qc}>
        <DelegateRunsSection taskId={1} />
      </QueryClientProvider>,
    );

    // 親委譲のカードが表示される（子が 2 つ）
    await screen.findByText('親委譲');
    // テキスト マッチングで「2 children」が見つかる
    expect(screen.getByText('2 children')).toBeInTheDocument();
  });

  it('子に失敗がある場合、赤いバッジで失敗数を表示', async () => {
    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
    render(
      <QueryClientProvider client={qc}>
        <DelegateRunsSection taskId={1} />
      </QueryClientProvider>,
    );

    // 失敗した親委譲のバッジが赤く、失敗数を含むテキストが表示される
    await screen.findByText('失敗した親委譲');
    // バッジに「1 children, 1 failed」が表示される
    expect(screen.getByText('1 children, 1 failed')).toBeInTheDocument();
    // そのバッジが赤系背景を持つ
    const badge = screen.getByText('1 children, 1 failed');
    expect(badge).toHaveClass('bg-red-100', 'text-red-800');
  });

  it('子が実行中の場合、青いバッジで件数を表示', async () => {
    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
    render(
      <QueryClientProvider client={qc}>
        <DelegateRunsSection taskId={1} />
      </QueryClientProvider>,
    );

    // 実行中の親委譲のカードが表示される
    await screen.findByText('実行中の親委譲');
    // 青いバッジに「1 children」が表示される
    const badge = Array.from(screen.getAllByText('1 children')).find((el) =>
      el.className.includes('bg-blue-100')
    );
    expect(badge).toBeDefined();
    expect(badge).toHaveClass('bg-blue-100', 'text-blue-800');
  });

  it('子がない親カードはバッジが表示されない', async () => {
    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
    render(
      <QueryClientProvider client={qc}>
        <DelegateRunsSection taskId={1} />
      </QueryClientProvider>,
    );

    // 結果なし委譲（子なし）のカードが表示される
    await screen.findByText('結果なし委譲');
    // 「children」というテキストは結果なし委譲のカード周辺には無いはず
    // ※ 他の親が children を表示しているので、全体ではある
    const resultCard = screen.getByText('結果なし委譲').closest('button');
    expect(resultCard?.textContent).not.toMatch(/children/);
  });

  it('すべての親カードが見える', async () => {
    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
    render(
      <QueryClientProvider client={qc}>
        <DelegateRunsSection taskId={1} />
      </QueryClientProvider>,
    );

    // 複数の親カードが表示される
    await screen.findByText('親委譲');
    expect(screen.getByText('親委譲')).toBeInTheDocument();
    expect(screen.getByText('失敗した親委譲')).toBeInTheDocument();
    expect(screen.getByText('結果なし委譲')).toBeInTheDocument();
    expect(screen.getByText('実行中の親委譲')).toBeInTheDocument();
  });

  it('totalTokens > 0 のカードに「1.2k tok」形式のトークン数が表示される', async () => {
    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
    render(
      <QueryClientProvider client={qc}>
        <DelegateRunsSection taskId={1} />
      </QueryClientProvider>,
    );

    // totalTokens = 1234 の「親委譲」カードを探す（フォーマットされて「1.2k tok」になるはず）
    await screen.findByText('親委譲');
    // そのカード内にトークン表示が含まれるかを確認
    const parentCard = screen.getByText('親委譲').closest('button');
    expect(parentCard?.textContent).toContain('1.2k tok');
  });

  it('totalTokens = 0 のカードにトークン数は表示されない', async () => {
    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
    render(
      <QueryClientProvider client={qc}>
        <DelegateRunsSection taskId={1} />
      </QueryClientProvider>,
    );

    // totalTokens = 0 の「結果なし委譲」カードを探す
    await screen.findByText('結果なし委譲');
    const card = screen.getByText('結果なし委譲').closest('button');
    // そのカード内に「tok」という文字列は無いはず
    expect(card?.textContent).not.toMatch(/\d+.*tok|tok.*\d+/);
  });

  it('カードを開くと aria-expanded=true がボタンに付く', async () => {
    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
    render(
      <QueryClientProvider client={qc}>
        <DelegateRunsSection taskId={1} />
      </QueryClientProvider>,
    );

    const buttons = await screen.findAllByRole('button');
    const parentButton = buttons[0];
    // 初期状態（閉じた）で aria-expanded=false
    expect(parentButton).toHaveAttribute('aria-expanded', 'false');

    // クリックして開く
    await userEvent.click(parentButton);
    expect(parentButton).toHaveAttribute('aria-expanded', 'true');
  });

  it('25 件のイベントを返すモックでカードを開きイベントトグルを押すと EventLine が 20 件＋「他 5 件」案内が出る', async () => {
    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
    const events = Array.from({ length: 25 }, (_, i) => ({
      eventId: `event${i}`,
      ts: new Date(2026, 0, 1, 0, 0, i).toISOString(),
      seq: i,
      kind: 'tool_call',
      payload: { tool: 'Read' },
    }));
    vi.mocked(api.fetchDelegateRunTimeline).mockResolvedValueOnce(events);

    render(
      <QueryClientProvider client={qc}>
        <DelegateRunsSection taskId={1} />
      </QueryClientProvider>,
    );

    // 親委譲のカードを開く
    await screen.findByText('親委譲');
    const parentButton = screen.getByText('親委譲').closest('button') as HTMLButtonElement;
    await userEvent.click(parentButton);

    // イベントトグルを押す
    const eventsToggle = await screen.findByText(/Detailed events/i);
    await userEvent.click(eventsToggle);

    // 「他 5 件」というテキストが表示される
    expect(screen.getByText(/5 more events/i)).toBeInTheDocument();
  });

  it('20 件以下のイベントなら「他 N 件」案内は出ない', async () => {
    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
    const events = Array.from({ length: 10 }, (_, i) => ({
      eventId: `event${i}`,
      ts: new Date(2026, 0, 1, 0, 0, i).toISOString(),
      seq: i,
      kind: 'tool_call',
      payload: { tool: 'Read' },
    }));
    vi.mocked(api.fetchDelegateRunTimeline).mockResolvedValueOnce(events);

    render(
      <QueryClientProvider client={qc}>
        <DelegateRunsSection taskId={1} />
      </QueryClientProvider>,
    );

    // 親委譲のカードを開く
    await screen.findByText('親委譲');
    const parentButton = screen.getByText('親委譲').closest('button') as HTMLButtonElement;
    await userEvent.click(parentButton);

    // イベントトグルを押す
    const eventsToggle = await screen.findByText(/Detailed events/i);
    await userEvent.click(eventsToggle);

    // 「more events」というテキストは表示されない
    expect(screen.queryByText(/more events/i)).not.toBeInTheDocument();
  });

  it('toolSummary ありの run でチップが表示される', async () => {
    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
    render(
      <QueryClientProvider client={qc}>
        <DelegateRunsSection taskId={1} />
      </QueryClientProvider>,
    );

    // 親委譲のカードを開く（toolSummary が設定されている）
    await screen.findByText('親委譲');
    const parentButton = screen.getByText('親委譲').closest('button') as HTMLButtonElement;
    await userEvent.click(parentButton);

    // ツール名とカウントが表示される
    expect(screen.getByText('Write ×2')).toBeInTheDocument();
    expect(screen.getByText('Read ×1')).toBeInTheDocument();
  });

  it('toolSummary 空または undefined なら見出しごと出ない', async () => {
    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
    render(
      <QueryClientProvider client={qc}>
        <DelegateRunsSection taskId={1} />
      </QueryClientProvider>,
    );

    // toolSummary がない「結果なし委譲」のカードを開く
    await screen.findByText('結果なし委譲');
    const card = screen.getByText('結果なし委譲').closest('button') as HTMLButtonElement;
    await userEvent.click(card);

    // 「Tools used」テキストは見つからない
    expect(screen.queryByText(/Tools used/i)).not.toBeInTheDocument();
  });

  it('filesChanged ありの run でパスが表示される', async () => {
    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
    render(
      <QueryClientProvider client={qc}>
        <DelegateRunsSection taskId={1} />
      </QueryClientProvider>,
    );

    // 親委譲のカードを開く（filesChanged が設定されている）
    await screen.findByText('親委譲');
    const parentButton = screen.getByText('親委譲').closest('button') as HTMLButtonElement;
    await userEvent.click(parentButton);

    // ファイルパスが表示される
    expect(screen.getByText('output/result.md')).toBeInTheDocument();
    expect(screen.getByText('output/summary.txt')).toBeInTheDocument();
  });

  it('filesChanged 空または undefined なら見出しごと出ない', async () => {
    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
    render(
      <QueryClientProvider client={qc}>
        <DelegateRunsSection taskId={1} />
      </QueryClientProvider>,
    );

    // filesChanged がない「結果なし委譲」のカードを開く
    await screen.findByText('結果なし委譲');
    const card = screen.getByText('結果なし委譲').closest('button') as HTMLButtonElement;
    await userEvent.click(card);

    // 「Changed files」テキストは見つからない
    expect(screen.queryByText(/Changed files/i)).not.toBeInTheDocument();
  });

  it('カードを開いた直後はイベント行が描画されず、トグルを押すと表示される', async () => {
    const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
    const events = Array.from({ length: 3 }, (_, i) => ({
      eventId: `event${i}`,
      ts: new Date(2026, 0, 1, 0, 0, i).toISOString(),
      seq: i,
      kind: 'tool_call',
      payload: { tool: 'Read' },
    }));
    vi.mocked(api.fetchDelegateRunTimeline).mockResolvedValueOnce(events);

    render(
      <QueryClientProvider client={qc}>
        <DelegateRunsSection taskId={1} />
      </QueryClientProvider>,
    );

    // 親委譲のカードを開く
    await screen.findByText('親委譲');
    const parentButton = screen.getByText('親委譲').closest('button') as HTMLButtonElement;
    await userEvent.click(parentButton);

    // イベントトグルを確認（デフォルトは閉じた状態）
    const eventsToggle = await screen.findByText(/Detailed events/i);
    expect(eventsToggle).toHaveAttribute('aria-expanded', 'false');

    // トグルを押す
    await userEvent.click(eventsToggle);

    // トグルが開いた状態になる
    expect(eventsToggle).toHaveAttribute('aria-expanded', 'true');
  });
});
