/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook } from '../../test-utils/render.js';
import { act } from 'react';
import {
  useSessionBrowser,
  convertSessionToHistoryFormats,
} from './useSessionBrowser.js';
import * as fs from 'node:fs/promises';
import path from 'node:path';
import { getSessionFiles, type SessionInfo } from '../../utils/sessionUtils.js';
import {
  type Config,
  type ConversationRecord,
  type MessageRecord,
  CoreToolCallStatus,
} from '@google/gemini-cli-core';
import {
  coreEvents,
  convertSessionToClientHistory,
  uiTelemetryService,
} from '@google/gemini-cli-core';

// Mock modules
vi.mock('fs/promises');
vi.mock('path');
vi.mock('../../utils/sessionUtils.js', async (importOriginal) => {
  const actual =
    await importOriginal<typeof import('../../utils/sessionUtils.js')>();
  return {
    ...actual,
    getSessionFiles: vi.fn(),
  };
});
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
  const actual =
    await importOriginal<typeof import('@google/gemini-cli-core')>();
  return {
    ...actual,
    uiTelemetryService: {
      clear: vi.fn(),
      hydrate: vi.fn(),
    },
  };
});

const MOCKED_PROJECT_TEMP_DIR = '/test/project/temp';
const MOCKED_CHATS_DIR = '/test/project/temp/chats';
const MOCKED_SESSION_ID = 'test-session-123';
const MOCKED_CURRENT_SESSION_ID = 'current-session-id';

describe('useSessionBrowser', () => {
  const mockedFs = vi.mocked(fs);
  const mockedPath = vi.mocked(path);
  const mockedGetSessionFiles = vi.mocked(getSessionFiles);

  const mockConfig = {
    storage: {
      getProjectTempDir: vi.fn(),
    },
    setSessionId: vi.fn(),
    getSessionId: vi.fn(),
    getGeminiClient: vi.fn().mockReturnValue({
      getChatRecordingService: vi.fn().mockReturnValue({
        deleteSession: vi.fn(),
      }),
    }),
  } as unknown as Config;

  const mockOnLoadHistory = vi.fn();

  beforeEach(() => {
    vi.resetAllMocks();
    vi.spyOn(coreEvents, 'emitFeedback').mockImplementation(() => {});
    mockedPath.join.mockImplementation((...args) => args.join('/'));
    vi.mocked(mockConfig.storage.getProjectTempDir).mockReturnValue(
      MOCKED_PROJECT_TEMP_DIR,
    );
    vi.mocked(mockConfig.getSessionId).mockReturnValue(
      MOCKED_CURRENT_SESSION_ID,
    );
  });

  it('should successfully resume a session', async () => {
    const MOCKED_FILENAME = 'session-2025-01-01-test-session-123.json';
    const mockConversation: ConversationRecord = {
      sessionId: 'existing-session-456',
      messages: [{ type: 'user', content: 'Hello' } as MessageRecord],
    } as ConversationRecord;

    const mockSession = {
      id: MOCKED_SESSION_ID,
      fileName: MOCKED_FILENAME,
    } as SessionInfo;
    mockedGetSessionFiles.mockResolvedValue([mockSession]);
    mockedFs.readFile.mockResolvedValue(JSON.stringify(mockConversation));

    const { result } = await renderHook(() =>
      useSessionBrowser(mockConfig, mockOnLoadHistory),
    );

    await act(async () => {
      await result.current.handleResumeSession(mockSession);
    });
    expect(mockedFs.readFile).toHaveBeenCalledWith(
      `${MOCKED_CHATS_DIR}/${MOCKED_FILENAME}`,
      'utf8',
    );
    expect(mockConfig.setSessionId).toHaveBeenCalledWith(
      'existing-session-456',
    );
    expect(uiTelemetryService.hydrate).toHaveBeenCalledWith(mockConversation);
    expect(result.current.isSessionBrowserOpen).toBe(false);
    expect(mockOnLoadHistory).toHaveBeenCalled();
  });

  it('should handle file read error', async () => {
    const MOCKED_FILENAME = 'session-2025-01-01-test-session-123.json';
    const mockSession = {
      id: MOCKED_SESSION_ID,
      fileName: MOCKED_FILENAME,
    } as SessionInfo;
    mockedFs.readFile.mockRejectedValue(new Error('File not found'));

    const { result } = await renderHook(() =>
      useSessionBrowser(mockConfig, mockOnLoadHistory),
    );

    await act(async () => {
      await result.current.handleResumeSession(mockSession);
    });

    expect(coreEvents.emitFeedback).toHaveBeenCalledWith(
      'error',
      'Error resuming session:',
      expect.any(Error),
    );
    expect(result.current.isSessionBrowserOpen).toBe(false);
  });

  it('should handle JSON parse error', async () => {
    const MOCKED_FILENAME = 'invalid.json';
    const mockSession = {
      id: MOCKED_SESSION_ID,
      fileName: MOCKED_FILENAME,
    } as SessionInfo;
    mockedFs.readFile.mockResolvedValue('invalid json');

    const { result } = await renderHook(() =>
      useSessionBrowser(mockConfig, mockOnLoadHistory),
    );

    await act(async () => {
      await result.current.handleResumeSession(mockSession);
    });

    expect(coreEvents.emitFeedback).toHaveBeenCalledWith(
      'error',
      'Error resuming session:',
      expect.any(Error),
    );
    expect(result.current.isSessionBrowserOpen).toBe(false);
  });
});

// The convertSessionToHistoryFormats tests are self-contained and do not need changes.
describe('convertSessionToHistoryFormats', () => {
  it('should convert empty messages array', () => {
    const result = convertSessionToHistoryFormats([]);
    expect(result.uiHistory).toEqual([]);
    expect(convertSessionToClientHistory([])).toEqual([]);
  });

  it('should convert basic user and model messages', () => {
    const messages: MessageRecord[] = [
      { type: 'user', content: 'Hello' } as MessageRecord,
      { type: 'gemini', content: 'Hi there' } as MessageRecord,
    ];

    const result = convertSessionToHistoryFormats(messages);

    expect(result.uiHistory).toHaveLength(2);
    expect(result.uiHistory[0]).toMatchObject({ type: 'user', text: 'Hello' });
    expect(result.uiHistory[1]).toMatchObject({
      type: 'gemini',
      text: 'Hi there',
    });

    const clientHistory = convertSessionToClientHistory(messages);
    expect(clientHistory).toHaveLength(2);
    expect(clientHistory[0]).toEqual({
      role: 'user',
      parts: [{ text: 'Hello' }],
    });
    expect(clientHistory[1]).toEqual({
      role: 'model',
      parts: [{ text: 'Hi there' }],
    });
  });

  it('should convert thinking tokens (thoughts) to thinking history items', () => {
    const messages: MessageRecord[] = [
      {
        type: 'gemini',
        content: 'Hi there',
        thoughts: [
          {
            subject: 'Thinking...',
            description: 'I should say hello.',
            timestamp: new Date().toISOString(),
          },
        ],
      } as MessageRecord,
    ];

    const result = convertSessionToHistoryFormats(messages);

    expect(result.uiHistory).toHaveLength(2);
    expect(result.uiHistory[0]).toMatchObject({
      type: 'thinking',
      thought: {
        subject: 'Thinking...',
        description: 'I should say hello.',
      },
    });
    expect(result.uiHistory[1]).toMatchObject({
      type: 'gemini',
      text: 'Hi there',
    });
  });

  it('should prioritize displayContent for UI history but use content for client history', () => {
    const messages: MessageRecord[] = [
      {
        type: 'user',
        content: [{ text: 'Expanded content' }],
        displayContent: [{ text: 'User input' }],
      } as MessageRecord,
    ];

    const result = convertSessionToHistoryFormats(messages);

    expect(result.uiHistory).toHaveLength(1);
    expect(result.uiHistory[0]).toMatchObject({
      type: 'user',
      text: 'User input',
    });

    const clientHistory = convertSessionToClientHistory(messages);
    expect(clientHistory).toHaveLength(1);
    expect(clientHistory[0]).toEqual({
      role: 'user',
      parts: [{ text: 'Expanded content' }],
    });
  });

  it('should filter out slash commands from client history but keep in UI', () => {
    const messages: MessageRecord[] = [
      { type: 'user', content: '/help' } as MessageRecord,
      { type: 'info', content: 'Help text' } as MessageRecord,
    ];

    const result = convertSessionToHistoryFormats(messages);

    expect(result.uiHistory).toHaveLength(2);
    expect(result.uiHistory[0]).toMatchObject({ type: 'user', text: '/help' });
    expect(result.uiHistory[1]).toMatchObject({
      type: 'info',
      text: 'Help text',
    });

    expect(convertSessionToClientHistory(messages)).toHaveLength(0);
  });

  it('should handle tool calls and responses', () => {
    const messages: MessageRecord[] = [
      { type: 'user', content: 'What time is it?' } as MessageRecord,
      {
        type: 'gemini',
        content: '',
        toolCalls: [
          {
            id: 'call_1',
            name: 'get_time',
            args: {},
            status: CoreToolCallStatus.Success,
            result: '12:00',
          },
        ],
      } as unknown as MessageRecord,
    ];

    const result = convertSessionToHistoryFormats(messages);

    expect(result.uiHistory).toHaveLength(2);
    expect(result.uiHistory[0]).toMatchObject({
      type: 'user',
      text: 'What time is it?',
    });
    expect(result.uiHistory[1]).toMatchObject({
      type: 'tool_group',
      tools: [
        expect.objectContaining({
          callId: 'call_1',
          name: 'get_time',
          status: CoreToolCallStatus.Success,
        }),
      ],
    });

    const clientHistory = convertSessionToClientHistory(messages);
    expect(clientHistory).toHaveLength(3); // User, Model (call), User (response)
    expect(clientHistory[0]).toEqual({
      role: 'user',
      parts: [{ text: 'What time is it?' }],
    });
    expect(clientHistory[1]).toEqual({
      role: 'model',
      parts: [
        {
          functionCall: {
            name: 'get_time',
            args: {},
            id: 'call_1',
          },
        },
      ],
    });
    expect(clientHistory[2]).toEqual({
      role: 'user',
      parts: [
        {
          functionResponse: {
            id: 'call_1',
            name: 'get_time',
            response: { output: '12:00' },
          },
        },
      ],
    });
  });
});
