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

import { act } from 'react';
import { renderHook } from '../../test-utils/render.js';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { useInputHistoryStore } from './useInputHistoryStore.js';
import { debugLogger } from '@google/gemini-cli-core';

describe('useInputHistoryStore', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should initialize with empty input history', () => {
    const { result } = renderHook(() => useInputHistoryStore());

    expect(result.current.inputHistory).toEqual([]);
  });

  it('should add input to history', () => {
    const { result } = renderHook(() => useInputHistoryStore());

    act(() => {
      result.current.addInput('test message 1');
    });

    expect(result.current.inputHistory).toEqual(['test message 1']);

    act(() => {
      result.current.addInput('test message 2');
    });

    expect(result.current.inputHistory).toEqual([
      'test message 1',
      'test message 2',
    ]);
  });

  it('should not add empty or whitespace-only inputs', () => {
    const { result } = renderHook(() => useInputHistoryStore());

    act(() => {
      result.current.addInput('');
    });

    expect(result.current.inputHistory).toEqual([]);

    act(() => {
      result.current.addInput('   ');
    });

    expect(result.current.inputHistory).toEqual([]);
  });

  it('should deduplicate consecutive identical messages', () => {
    const { result } = renderHook(() => useInputHistoryStore());

    act(() => {
      result.current.addInput('test message');
    });

    act(() => {
      result.current.addInput('test message'); // Same as previous
    });

    expect(result.current.inputHistory).toEqual(['test message']);

    act(() => {
      result.current.addInput('different message');
    });

    act(() => {
      result.current.addInput('test message'); // Same as first, but not consecutive
    });

    expect(result.current.inputHistory).toEqual([
      'test message',
      'different message',
      'test message',
    ]);
  });

  it('should initialize from logger successfully', async () => {
    const mockLogger = {
      getPreviousUserMessages: vi
        .fn()
        .mockResolvedValue(['newest', 'middle', 'oldest']),
    };

    const { result } = renderHook(() => useInputHistoryStore());

    await act(async () => {
      await result.current.initializeFromLogger(mockLogger);
    });

    // Should reverse the order to oldest first
    expect(result.current.inputHistory).toEqual(['oldest', 'middle', 'newest']);
    expect(mockLogger.getPreviousUserMessages).toHaveBeenCalledTimes(1);
  });

  it('should handle logger initialization failure gracefully', async () => {
    const mockLogger = {
      getPreviousUserMessages: vi
        .fn()
        .mockRejectedValue(new Error('Logger error')),
    };

    const consoleSpy = vi
      .spyOn(debugLogger, 'warn')
      .mockImplementation(() => {});

    const { result } = renderHook(() => useInputHistoryStore());

    await act(async () => {
      await result.current.initializeFromLogger(mockLogger);
    });

    expect(result.current.inputHistory).toEqual([]);
    expect(consoleSpy).toHaveBeenCalledWith(
      'Failed to initialize input history from logger:',
      expect.any(Error),
    );

    consoleSpy.mockRestore();
  });

  it('should initialize only once', async () => {
    const mockLogger = {
      getPreviousUserMessages: vi
        .fn()
        .mockResolvedValue(['message1', 'message2']),
    };

    const { result } = renderHook(() => useInputHistoryStore());

    // Call initializeFromLogger twice
    await act(async () => {
      await result.current.initializeFromLogger(mockLogger);
    });

    await act(async () => {
      await result.current.initializeFromLogger(mockLogger);
    });

    // Should be called only once
    expect(mockLogger.getPreviousUserMessages).toHaveBeenCalledTimes(1);
    expect(result.current.inputHistory).toEqual(['message2', 'message1']);
  });

  it('should handle null logger gracefully', async () => {
    const { result } = renderHook(() => useInputHistoryStore());

    await act(async () => {
      await result.current.initializeFromLogger(null);
    });

    expect(result.current.inputHistory).toEqual([]);
  });

  it('should trim input before adding to history', () => {
    const { result } = renderHook(() => useInputHistoryStore());

    act(() => {
      result.current.addInput('  test message  ');
    });

    expect(result.current.inputHistory).toEqual(['test message']);
  });

  describe('deduplication logic from previous implementation', () => {
    it('should deduplicate consecutive messages from past sessions during initialization', async () => {
      const mockLogger = {
        getPreviousUserMessages: vi
          .fn()
          .mockResolvedValue([
            'message1',
            'message1',
            'message2',
            'message2',
            'message3',
          ]), // newest first with duplicates
      };

      const { result } = renderHook(() => useInputHistoryStore());

      await act(async () => {
        await result.current.initializeFromLogger(mockLogger);
      });

      // Should deduplicate consecutive messages and reverse to oldest first
      expect(result.current.inputHistory).toEqual([
        'message3',
        'message2',
        'message1',
      ]);
    });

    it('should deduplicate across session boundaries', async () => {
      const mockLogger = {
        getPreviousUserMessages: vi.fn().mockResolvedValue(['old2', 'old1']), // newest first
      };

      const { result } = renderHook(() => useInputHistoryStore());

      // Initialize with past session
      await act(async () => {
        await result.current.initializeFromLogger(mockLogger);
      });

      // Add current session inputs
      act(() => {
        result.current.addInput('old2'); // Same as last past session message
      });

      // Should deduplicate across session boundary
      expect(result.current.inputHistory).toEqual(['old1', 'old2']);

      act(() => {
        result.current.addInput('new1');
      });

      expect(result.current.inputHistory).toEqual(['old1', 'old2', 'new1']);
    });

    it('should preserve non-consecutive duplicates', async () => {
      const mockLogger = {
        getPreviousUserMessages: vi
          .fn()
          .mockResolvedValue(['message2', 'message1', 'message2']), // newest first with non-consecutive duplicate
      };

      const { result } = renderHook(() => useInputHistoryStore());

      await act(async () => {
        await result.current.initializeFromLogger(mockLogger);
      });

      // Non-consecutive duplicates should be preserved
      expect(result.current.inputHistory).toEqual([
        'message2',
        'message1',
        'message2',
      ]);
    });

    it('should handle complex deduplication with current session', () => {
      const { result } = renderHook(() => useInputHistoryStore());

      // Add multiple messages with duplicates
      act(() => {
        result.current.addInput('hello');
      });
      act(() => {
        result.current.addInput('hello'); // consecutive duplicate
      });
      act(() => {
        result.current.addInput('world');
      });
      act(() => {
        result.current.addInput('world'); // consecutive duplicate
      });
      act(() => {
        result.current.addInput('hello'); // non-consecutive duplicate
      });

      // Should have deduplicated consecutive ones
      expect(result.current.inputHistory).toEqual(['hello', 'world', 'hello']);
    });

    it('should maintain oldest-first order in final output', async () => {
      const mockLogger = {
        getPreviousUserMessages: vi
          .fn()
          .mockResolvedValue(['newest', 'middle', 'oldest']), // newest first
      };

      const { result } = renderHook(() => useInputHistoryStore());

      await act(async () => {
        await result.current.initializeFromLogger(mockLogger);
      });

      // Add current session messages
      act(() => {
        result.current.addInput('current1');
      });
      act(() => {
        result.current.addInput('current2');
      });

      // Should maintain oldest-first order
      expect(result.current.inputHistory).toEqual([
        'oldest',
        'middle',
        'newest',
        'current1',
        'current2',
      ]);
    });
  });
});
