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

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as path from 'node:path';
import {
  resolvePathFromEnv,
  isSectionEnabled,
  applySubstitutions,
} from './utils.js';
import type { Config } from '../config/config.js';
import type { ToolRegistry } from '../tools/tool-registry.js';

vi.mock('../utils/paths.js', () => ({
  homedir: vi.fn().mockReturnValue('/mock/home'),
}));

vi.mock('../utils/debugLogger.js', () => ({
  debugLogger: {
    warn: vi.fn(),
  },
}));

vi.mock('./snippets.js', () => ({
  renderSubAgents: vi.fn().mockReturnValue('mocked-sub-agents'),
}));

vi.mock('./snippets.legacy.js', () => ({
  renderSubAgents: vi.fn().mockReturnValue('mocked-legacy-sub-agents'),
}));

describe('resolvePathFromEnv', () => {
  it('should return default values for undefined input', () => {
    const result = resolvePathFromEnv(undefined);
    expect(result).toEqual({
      isSwitch: false,
      value: null,
      isDisabled: false,
    });
  });

  it('should return default values for empty string input', () => {
    const result = resolvePathFromEnv('');
    expect(result).toEqual({
      isSwitch: false,
      value: null,
      isDisabled: false,
    });
  });

  it('should return default values for whitespace-only input', () => {
    const result = resolvePathFromEnv('   ');
    expect(result).toEqual({
      isSwitch: false,
      value: null,
      isDisabled: false,
    });
  });

  it('should recognize "true" as an enabled switch', () => {
    const result = resolvePathFromEnv('true');
    expect(result).toEqual({
      isSwitch: true,
      value: 'true',
      isDisabled: false,
    });
  });

  it('should recognize "1" as an enabled switch', () => {
    const result = resolvePathFromEnv('1');
    expect(result).toEqual({
      isSwitch: true,
      value: '1',
      isDisabled: false,
    });
  });

  it('should recognize "false" as a disabled switch', () => {
    const result = resolvePathFromEnv('false');
    expect(result).toEqual({
      isSwitch: true,
      value: 'false',
      isDisabled: true,
    });
  });

  it('should recognize "0" as a disabled switch', () => {
    const result = resolvePathFromEnv('0');
    expect(result).toEqual({
      isSwitch: true,
      value: '0',
      isDisabled: true,
    });
  });

  it('should handle case-insensitive switch values', () => {
    const result = resolvePathFromEnv('TRUE');
    expect(result).toEqual({
      isSwitch: true,
      value: 'true',
      isDisabled: false,
    });
  });

  it('should handle case-insensitive FALSE', () => {
    const result = resolvePathFromEnv('FALSE');
    expect(result).toEqual({
      isSwitch: true,
      value: 'false',
      isDisabled: true,
    });
  });

  it('should trim whitespace before evaluating switch values', () => {
    const result = resolvePathFromEnv('  true  ');
    expect(result).toEqual({
      isSwitch: true,
      value: 'true',
      isDisabled: false,
    });
  });

  it('should resolve a regular path', () => {
    const p = path.resolve('/some/absolute/path');
    const result = resolvePathFromEnv(p);
    expect(result.isSwitch).toBe(false);
    expect(result.value).toBe(p);
    expect(result.isDisabled).toBe(false);
  });

  it('should resolve a tilde path to the home directory', () => {
    const result = resolvePathFromEnv('~/my/custom/path');
    expect(result.isSwitch).toBe(false);
    expect(result.value).toContain(path.normalize('/mock/home'));
    expect(result.value).toContain(path.normalize('my/custom/path'));
    expect(result.isDisabled).toBe(false);
  });

  it('should resolve a bare tilde to the home directory', () => {
    const result = resolvePathFromEnv('~');
    expect(result.isSwitch).toBe(false);
    expect(result.value).toBe(path.resolve('/mock/home'));
    expect(result.isDisabled).toBe(false);
  });

  it('should handle home directory resolution failure gracefully', async () => {
    const { homedir } = await import('../utils/paths.js');
    vi.mocked(homedir).mockImplementationOnce(() => {
      throw new Error('No home directory');
    });

    const result = resolvePathFromEnv('~/some/path');
    expect(result).toEqual({
      isSwitch: false,
      value: null,
      isDisabled: false,
    });
  });
});

describe('isSectionEnabled', () => {
  afterEach(() => {
    vi.unstubAllEnvs();
  });

  it('should return true when the env var is not set', () => {
    expect(isSectionEnabled('SOME_KEY')).toBe(true);
  });

  it('should return true when the env var is set to "1"', () => {
    vi.stubEnv('GEMINI_PROMPT_SOME_KEY', '1');
    expect(isSectionEnabled('SOME_KEY')).toBe(true);
  });

  it('should return true when the env var is set to "true"', () => {
    vi.stubEnv('GEMINI_PROMPT_SOME_KEY', 'true');
    expect(isSectionEnabled('SOME_KEY')).toBe(true);
  });

  it('should return false when the env var is set to "0"', () => {
    vi.stubEnv('GEMINI_PROMPT_SOME_KEY', '0');
    expect(isSectionEnabled('SOME_KEY')).toBe(false);
  });

  it('should return false when the env var is set to "false"', () => {
    vi.stubEnv('GEMINI_PROMPT_SOME_KEY', 'false');
    expect(isSectionEnabled('SOME_KEY')).toBe(false);
  });

  it('should handle case-insensitive key conversion', () => {
    vi.stubEnv('GEMINI_PROMPT_MY_SECTION', '0');
    expect(isSectionEnabled('my_section')).toBe(false);
  });

  it('should handle whitespace around the env var value', () => {
    vi.stubEnv('GEMINI_PROMPT_SOME_KEY', '  false  ');
    expect(isSectionEnabled('SOME_KEY')).toBe(false);
  });

  it('should return true for any non-falsy value', () => {
    vi.stubEnv('GEMINI_PROMPT_SOME_KEY', 'enabled');
    expect(isSectionEnabled('SOME_KEY')).toBe(true);
  });
});

describe('applySubstitutions', () => {
  let mockConfig: Config;

  beforeEach(() => {
    mockConfig = {
      get config() {
        return this;
      },
      toolRegistry: {
        getAllToolNames: vi.fn().mockReturnValue([]),
        getAllTools: vi.fn().mockReturnValue([]),
      },
      getAgentRegistry: vi.fn().mockReturnValue({
        getAllDefinitions: vi.fn().mockReturnValue([]),
        getDefinition: vi.fn().mockReturnValue(undefined),
      }),
      getToolRegistry: vi.fn().mockReturnValue({
        getAllToolNames: vi.fn().mockReturnValue([]),
      }),
    } as unknown as Config;
  });

  it('should replace ${AgentSkills} with the skills prompt', () => {
    const result = applySubstitutions(
      'Skills: ${AgentSkills}',
      mockConfig,
      'my-skills-content',
    );
    expect(result).toBe('Skills: my-skills-content');
  });

  it('should replace multiple ${AgentSkills} occurrences', () => {
    const result = applySubstitutions(
      '${AgentSkills} and ${AgentSkills}',
      mockConfig,
      'skills',
    );
    expect(result).toBe('skills and skills');
  });

  it('should replace ${SubAgents} with rendered sub-agents content', () => {
    const result = applySubstitutions(
      'Agents: ${SubAgents}',
      mockConfig,
      '',
      true,
    );
    expect(result).toContain('mocked-sub-agents');
  });

  it('should use legacy snippets when isGemini3 is false', () => {
    const result = applySubstitutions(
      'Agents: ${SubAgents}',
      mockConfig,
      '',
      false,
    );
    expect(result).toContain('mocked-legacy-sub-agents');
  });

  it('should replace ${AvailableTools} with tool names list', () => {
    (mockConfig as unknown as { toolRegistry: ToolRegistry }).toolRegistry = {
      getAllToolNames: vi.fn().mockReturnValue(['read_file', 'write_file']),
      getAllTools: vi.fn().mockReturnValue([]),
    } as unknown as ToolRegistry;

    const result = applySubstitutions(
      'Tools: ${AvailableTools}',
      mockConfig,
      '',
    );
    expect(result).toContain('- read_file');
    expect(result).toContain('- write_file');
  });

  it('should show no tools message when no tools available', () => {
    const result = applySubstitutions(
      'Tools: ${AvailableTools}',
      mockConfig,
      '',
    );
    expect(result).toContain('No tools are currently available.');
  });

  it('should replace tool-specific ${toolName_ToolName} variables', () => {
    (mockConfig as unknown as { toolRegistry: ToolRegistry }).toolRegistry = {
      getAllToolNames: vi.fn().mockReturnValue(['read_file']),
      getAllTools: vi.fn().mockReturnValue([]),
    } as unknown as ToolRegistry;

    const result = applySubstitutions(
      'Use ${read_file_ToolName} to read',
      mockConfig,
      '',
    );
    expect(result).toBe('Use read_file to read');
  });

  it('should handle a prompt with no substitution placeholders', () => {
    const result = applySubstitutions(
      'A plain prompt with no variables.',
      mockConfig,
      '',
    );
    expect(result).toBe('A plain prompt with no variables.');
  });
});
