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

import { renderWithProviders } from '../../test-utils/render.js';
import { waitFor } from '../../test-utils/async.js';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { act } from 'react';
import { AgentConfigDialog } from './AgentConfigDialog.js';
import { LoadedSettings, SettingScope } from '../../config/settings.js';
import type { AgentDefinition } from '@google/gemini-cli-core';

enum TerminalKeys {
  ENTER = '\u000D',
  TAB = '\t',
  UP_ARROW = '\u001B[A',
  DOWN_ARROW = '\u001B[B',
  ESCAPE = '\u001B',
}

const createMockSettings = (
  userSettings = {},
  workspaceSettings = {},
): LoadedSettings => {
  const settings = new LoadedSettings(
    {
      settings: { ui: { customThemes: {} }, mcpServers: {}, agents: {} },
      originalSettings: {
        ui: { customThemes: {} },
        mcpServers: {},
        agents: {},
      },
      path: '/system/settings.json',
    },
    {
      settings: {},
      originalSettings: {},
      path: '/system/system-defaults.json',
    },
    {
      settings: {
        ui: { customThemes: {} },
        mcpServers: {},
        agents: { overrides: {} },
        ...userSettings,
      },
      originalSettings: {
        ui: { customThemes: {} },
        mcpServers: {},
        agents: { overrides: {} },
        ...userSettings,
      },
      path: '/user/settings.json',
    },
    {
      settings: {
        ui: { customThemes: {} },
        mcpServers: {},
        agents: { overrides: {} },
        ...workspaceSettings,
      },
      originalSettings: {
        ui: { customThemes: {} },
        mcpServers: {},
        agents: { overrides: {} },
        ...workspaceSettings,
      },
      path: '/workspace/settings.json',
    },
    true,
    [],
  );

  // Mock setValue
  settings.setValue = vi.fn();

  return settings;
};

const createMockAgentDefinition = (
  overrides: Partial<AgentDefinition> = {},
): AgentDefinition =>
  ({
    name: 'test-agent',
    displayName: 'Test Agent',
    description: 'A test agent for testing',
    kind: 'local',
    modelConfig: {
      model: 'inherit',
      generateContentConfig: {
        temperature: 1.0,
      },
    },
    runConfig: {
      maxTimeMinutes: 5,
      maxTurns: 10,
    },
    experimental: false,
    ...overrides,
  }) as AgentDefinition;

describe('AgentConfigDialog', () => {
  let mockOnClose: ReturnType<typeof vi.fn>;
  let mockOnSave: ReturnType<typeof vi.fn>;

  beforeEach(() => {
    vi.clearAllMocks();
    mockOnClose = vi.fn();
    mockOnSave = vi.fn();
  });

  const renderDialog = async (
    settings: LoadedSettings,
    definition: AgentDefinition = createMockAgentDefinition(),
  ) => {
    const result = await renderWithProviders(
      <AgentConfigDialog
        agentName="test-agent"
        displayName="Test Agent"
        definition={definition}
        settings={settings}
        onClose={mockOnClose}
        onSave={mockOnSave}
      />,
      { settings, uiState: { mainAreaWidth: 100 } },
    );
    return result;
  };

  describe('rendering', () => {
    it('should render the dialog with title', async () => {
      const settings = createMockSettings();
      const { lastFrame, unmount } = await renderDialog(settings);
      expect(lastFrame()).toContain('Configure: Test Agent');
      unmount();
    });

    it('should render all configuration fields', async () => {
      const settings = createMockSettings();
      const { lastFrame, unmount } = await renderDialog(settings);
      const frame = lastFrame();

      expect(frame).toContain('Enabled');
      expect(frame).toContain('Model');
      expect(frame).toContain('Temperature');
      expect(frame).toContain('Top P');
      expect(frame).toContain('Top K');
      expect(frame).toContain('Max Output Tokens');
      expect(frame).toContain('Max Time (minutes)');
      expect(frame).toContain('Max Turns');
      unmount();
    });

    it('should render scope selector', async () => {
      const settings = createMockSettings();
      const { lastFrame, unmount } = await renderDialog(settings);

      expect(lastFrame()).toContain('Apply To');
      expect(lastFrame()).toContain('User Settings');
      expect(lastFrame()).toContain('Workspace Settings');
      unmount();
    });

    it('should render help text', async () => {
      const settings = createMockSettings();
      const { lastFrame, unmount } = await renderDialog(settings);

      expect(lastFrame()).toContain('Use Enter to select');
      expect(lastFrame()).toContain('Tab to change focus');
      expect(lastFrame()).toContain('Esc to close');
      unmount();
    });
  });

  describe('keyboard navigation', () => {
    it('should close dialog on Escape', async () => {
      const settings = createMockSettings();
      const { stdin, waitUntilReady, unmount } = await renderDialog(settings);

      await act(async () => {
        stdin.write(TerminalKeys.ESCAPE);
      });
      // Escape key has a 50ms timeout in KeypressContext, so we need to wrap waitUntilReady in act
      await act(async () => {
        await waitUntilReady();
      });

      await waitFor(() => {
        expect(mockOnClose).toHaveBeenCalled();
      });
      unmount();
    });

    it('should navigate down with arrow key', async () => {
      const settings = createMockSettings();
      const { lastFrame, stdin, waitUntilReady, unmount } =
        await renderDialog(settings);

      // Initially first item (Enabled) should be active
      expect(lastFrame()).toContain('●');

      // Press down arrow
      await act(async () => {
        stdin.write(TerminalKeys.DOWN_ARROW);
      });
      await waitUntilReady();

      await waitFor(() => {
        // Model field should now be highlighted
        expect(lastFrame()).toContain('Model');
      });
      unmount();
    });

    it('should switch focus with Tab', async () => {
      const settings = createMockSettings();
      const { lastFrame, stdin, waitUntilReady, unmount } =
        await renderDialog(settings);

      // Initially settings section is focused
      expect(lastFrame()).toContain('> Configure: Test Agent');

      // Press Tab to switch to scope selector
      await act(async () => {
        stdin.write(TerminalKeys.TAB);
      });
      await waitUntilReady();

      await waitFor(() => {
        expect(lastFrame()).toContain('> Apply To');
      });
      unmount();
    });
  });

  describe('boolean toggle', () => {
    it('should toggle enabled field on Enter', async () => {
      const settings = createMockSettings();
      const { stdin, waitUntilReady, unmount } = await renderDialog(settings);

      // Press Enter to toggle the first field (Enabled)
      await act(async () => {
        stdin.write(TerminalKeys.ENTER);
      });
      await waitUntilReady();

      await waitFor(() => {
        expect(settings.setValue).toHaveBeenCalledWith(
          SettingScope.User,
          'agents.overrides.test-agent.enabled',
          false, // Toggles from true (default) to false
        );
        expect(mockOnSave).toHaveBeenCalled();
      });
      unmount();
    });
  });

  describe('default values', () => {
    it('should show values from agent definition as defaults', async () => {
      const definition = createMockAgentDefinition({
        modelConfig: {
          model: 'gemini-2.0-flash',
          generateContentConfig: {
            temperature: 0.7,
          },
        },
        runConfig: {
          maxTimeMinutes: 10,
          maxTurns: 20,
        },
      });
      const settings = createMockSettings();
      const { lastFrame, unmount } = await renderDialog(settings, definition);
      const frame = lastFrame();

      expect(frame).toContain('gemini-2.0-flash');
      expect(frame).toContain('0.7');
      expect(frame).toContain('10');
      expect(frame).toContain('20');
      unmount();
    });

    it('should show experimental agents as disabled by default', async () => {
      const definition = createMockAgentDefinition({
        experimental: true,
      });
      const settings = createMockSettings();
      const { lastFrame, unmount } = await renderDialog(settings, definition);

      // Experimental agents default to disabled
      expect(lastFrame()).toContain('false');
      unmount();
    });
  });

  describe('existing overrides', () => {
    it('should show existing override values with * indicator', async () => {
      const settings = createMockSettings({
        agents: {
          overrides: {
            'test-agent': {
              enabled: false,
              modelConfig: {
                model: 'custom-model',
              },
            },
          },
        },
      });
      const { lastFrame, unmount } = await renderDialog(settings);
      const frame = lastFrame();

      // Should show the overridden values
      expect(frame).toContain('custom-model');
      expect(frame).toContain('false');
      unmount();
    });
    it('should respond to availableTerminalHeight and truncate list', async () => {
      const settings = createMockSettings();
      // Agent config has about 6 base items + 2 per tool
      // Render with very small height (20)
      const { lastFrame, unmount } = await renderWithProviders(
        <AgentConfigDialog
          agentName="test-agent"
          displayName="Test Agent"
          definition={createMockAgentDefinition()}
          settings={settings}
          onClose={mockOnClose}
          onSave={mockOnSave}
          availableTerminalHeight={20}
        />,
        { settings, uiState: { mainAreaWidth: 100 } },
      );
      await waitFor(() =>
        expect(lastFrame()).toContain('Configure: Test Agent'),
      );

      const frame = lastFrame();
      // At height 20, it should be heavily truncated and show '▼'
      expect(frame).toContain('▼');
      unmount();
    });
  });
});
