import { describe, it, expect, vi } from 'vitest';
import { formatPrompt, parseCommand } from './minimal.js';
import type { ModelConfig } from '../../config/schema.js';
import type { ManagedSession } from '../../session/index.js';
import type { ModelClient } from '../../models/types.js';
import type { ModelRouter } from '../../models/router.js';
import type { NativeAgent } from '../../backends/native/agent.js';
import { MinimalTui } from './minimal.js';

type TuiRouterStub = Pick<ModelRouter, 'getTier' | 'getAvailableTiers' | 'setTier' | 'getLabel'> &
  Partial<ModelRouter> &
  Partial<ModelClient> & {
    getLocalProviderName: () => string | undefined;
    setLocalClient: ReturnType<typeof vi.fn>;
  };

function asSession(value: unknown): ManagedSession {
  return value as ManagedSession;
}

function asRouter(value: unknown): ModelClient & ModelRouter {
  return value as ModelClient & ModelRouter;
}

function asModelClient(value: unknown): ModelClient {
  return value as ModelClient;
}

function asAgent(value: unknown): NativeAgent {
  return value as NativeAgent;
}

function minimalTuiPrivates(value: MinimalTui): {
  handleBackendCommand: (provider: string) => Promise<void>;
  handleModelCommand: (tier: string, providerModel?: string) => void;
  handleContextCommand: () => void;
  handleVerboseCommand: () => void;
  handleToolEvent: (event: unknown) => void;
  handleCommand: (command: unknown) => Promise<void>;
  handleEscapeAction: () => boolean;
  prompt: (text: string) => Promise<string>;
  rl: {
    once: (event: string, cb: () => void) => void;
    removeListener: (event: string, cb: () => void) => void;
    question: (text: string, cb: (answer: string) => void) => void;
    write: (data: string | null, key?: { ctrl?: boolean; name?: string }) => void;
  };
  activePromptCancel: (() => void) | null;
  activeOperationCancel: (() => void) | null;
} {
  return value as unknown as {
    handleBackendCommand: (provider: string) => Promise<void>;
    handleModelCommand: (tier: string, providerModel?: string) => void;
    handleContextCommand: () => void;
    handleVerboseCommand: () => void;
    handleToolEvent: (event: unknown) => void;
    handleCommand: (command: unknown) => Promise<void>;
    handleEscapeAction: () => boolean;
    prompt: (text: string) => Promise<string>;
    rl: {
      once: (event: string, cb: () => void) => void;
      removeListener: (event: string, cb: () => void) => void;
      question: (text: string, cb: (answer: string) => void) => void;
      write: (data: string | null, key?: { ctrl?: boolean; name?: string }) => void;
    };
    activePromptCancel: (() => void) | null;
    activeOperationCancel: (() => void) | null;
  };
}

describe('formatPrompt', () => {
  it('formats default prompt', () => {
    const prompt = formatPrompt('default');
    expect(prompt).toContain('flynn>');
  });

  it('formats thinking prompt', () => {
    const prompt = formatPrompt('thinking');
    expect(prompt).toContain('flynn...');
  });
});

describe('parseCommand (re-exported)', () => {
  it('parses /quit command', () => {
    const result = parseCommand('/quit');
    expect(result).toEqual({ type: 'quit' });
  });

  it('parses /model command', () => {
    const result = parseCommand('/model local');
    expect(result).toEqual({ type: 'model', name: 'local' });
  });

  it('parses regular message', () => {
    const result = parseCommand('Hello, Flynn!');
    expect(result).toEqual({ type: 'message', content: 'Hello, Flynn!' });
  });

  it('returns null for empty input', () => {
    const result = parseCommand('');
    expect(result).toBeNull();
  });
});

describe('MinimalTui backend command', () => {
  it('switches local backend when provider is configured', async () => {
    const mockSession = {
      id: 'test',
      getHistory: () => [],
      addMessage: vi.fn(),
      clear: vi.fn(),
      replaceHistory: vi.fn(),
    };

    const mockRouter: TuiRouterStub = {
      getTier: () => 'default' as const,
      getAvailableTiers: () => ['default', 'local'],
      setTier: vi.fn(() => true),
      getLabel: (tier: string) => tier,
      getLocalProviderName: () => 'ollama',
      setLocalClient: vi.fn(),
      chat: vi.fn(),
      getClient: vi.fn(),
    };

    const localProviders: Record<string, ModelConfig> = {
      llamacpp: {
        provider: 'llamacpp',
        model: '',
        endpoint: 'http://localhost:8080',
      },
    };

    const tui = new MinimalTui({
      session: asSession(mockSession),
      modelClient: asRouter(mockRouter),
      modelRouter: asRouter(mockRouter),
      systemPrompt: 'test',
      localProviders,
    });

    // Access private method for testing
    await minimalTuiPrivates(tui).handleBackendCommand('llamacpp');

    expect(mockRouter.setLocalClient).toHaveBeenCalled();
  });

  it('syncs agent tier when /model command switches tier', () => {
    const mockSession = {
      id: 'test',
      getHistory: () => [],
      addMessage: vi.fn(),
      clear: vi.fn(),
      replaceHistory: vi.fn(),
    };

    const mockRouter: TuiRouterStub = {
      getTier: () => 'default' as const,
      getAvailableTiers: () => ['default', 'local'],
      setTier: vi.fn(() => true),
      getLabel: (tier: string) => tier,
      getLocalProviderName: () => 'ollama',
      setLocalClient: vi.fn(),
      chat: vi.fn(),
      getClient: vi.fn(),
    };

    const mockAgent = {
      setModelTier: vi.fn(),
      getModelTier: vi.fn(() => 'default'),
      process: vi.fn(),
    };

    const tui = new MinimalTui({
      session: asSession(mockSession),
      modelClient: asRouter(mockRouter),
      modelRouter: asRouter(mockRouter),
      agent: asAgent(mockAgent),
      systemPrompt: 'test',
    });

    // Call private handleModelCommand to switch to local
    minimalTuiPrivates(tui).handleModelCommand('local');

    expect(mockRouter.setTier).toHaveBeenCalledWith('local');
    expect(mockAgent.setModelTier).toHaveBeenCalledWith('local');
  });

  it('uses configured compaction threshold in /context output', () => {
    const mockSession = {
      id: 'test',
      getHistory: () => [{ role: 'user', content: 'x'.repeat(400) }],
      addMessage: vi.fn(),
      clear: vi.fn(),
      replaceHistory: vi.fn(),
    };

    const mockRouter: TuiRouterStub = {
      getTier: () => 'default' as const,
      getAvailableTiers: () => ['default'],
      setTier: vi.fn(() => true),
      getLabel: () => 'gpt-4o',
      getLocalProviderName: () => 'ollama',
      setLocalClient: vi.fn(),
      chat: vi.fn(),
      getClient: vi.fn(),
    };

    const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
    try {
      const tui = new MinimalTui({
        session: asSession(mockSession),
        modelClient: asRouter(mockRouter),
        modelRouter: asRouter(mockRouter),
        systemPrompt: 'test',
        contextThresholdPct: 67,
      });

      minimalTuiPrivates(tui).handleContextCommand();
      expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('compaction threshold: 67%'));
    } finally {
      logSpy.mockRestore();
    }
  });

  it('reuses configured provider credentials for /model <tier> <provider/model>', () => {
    const prevOpenRouterKey = process.env.OPENROUTER_API_KEY;
    delete process.env.OPENROUTER_API_KEY;

    try {
      const mockSession = {
        id: 'test',
        getHistory: () => [],
        addMessage: vi.fn(),
        clear: vi.fn(),
        replaceHistory: vi.fn(),
      };

      const mockRouter: TuiRouterStub = {
        getTier: () => 'default' as const,
        getAvailableTiers: () => ['default', 'local'],
        setTier: vi.fn(() => true),
        getLabel: (tier: string) => tier,
        getLocalProviderName: () => 'ollama',
        setLocalClient: vi.fn(),
        setClient: vi.fn(),
        setTierStrict: vi.fn(),
        chat: vi.fn(),
        getClient: vi.fn(),
      };

      const tui = new MinimalTui({
        session: asSession(mockSession),
        modelClient: asRouter(mockRouter),
        modelRouter: asRouter(mockRouter),
        systemPrompt: 'test',
        modelProviderConfigs: {
          openrouter: {
            provider: 'openrouter',
            model: 'seed-model',
            api_key: 'test-key',
            endpoint: 'https://openrouter.ai/api/v1',
          },
        },
      });

      minimalTuiPrivates(tui).handleModelCommand('default', 'openrouter/deepseek/deepseek-chat');

      expect(mockRouter.setClient).toHaveBeenCalledOnce();
      expect(mockRouter.setTierStrict).toHaveBeenCalledWith('default', true);
      expect(mockRouter.setTier).toHaveBeenCalledWith('default');
    } finally {
      if (prevOpenRouterKey) {
        process.env.OPENROUTER_API_KEY = prevOpenRouterKey;
      } else {
        delete process.env.OPENROUTER_API_KEY;
      }
    }
  });

  it('switches active tier and syncs agent for /model <tier> <provider/model>', () => {
    const prevOpenRouterKey = process.env.OPENROUTER_API_KEY;
    delete process.env.OPENROUTER_API_KEY;

    try {
      const mockSession = {
        id: 'test',
        getHistory: () => [],
        addMessage: vi.fn(),
        clear: vi.fn(),
        replaceHistory: vi.fn(),
      };

      const mockRouter: TuiRouterStub = {
        getTier: () => 'fast' as const,
        getAvailableTiers: () => ['default', 'fast', 'local'],
        setTier: vi.fn(() => true),
        getLabel: (tier: string) => tier,
        getLocalProviderName: () => 'ollama',
        setLocalClient: vi.fn(),
        setClient: vi.fn(),
        setTierStrict: vi.fn(),
        chat: vi.fn(),
        getClient: vi.fn(),
      };

      const mockAgent = {
        setModelTier: vi.fn(),
        getModelTier: vi.fn(() => 'fast'),
        process: vi.fn(),
      };

      const tui = new MinimalTui({
        session: asSession(mockSession),
        modelClient: asRouter(mockRouter),
        modelRouter: asRouter(mockRouter),
        agent: asAgent(mockAgent),
        systemPrompt: 'test',
        modelProviderConfigs: {
          openrouter: {
            provider: 'openrouter',
            model: 'seed-model',
            api_key: 'test-key',
            endpoint: 'https://openrouter.ai/api/v1',
          },
        },
      });

      minimalTuiPrivates(tui).handleModelCommand('default', 'openrouter/deepseek/deepseek-chat');

      expect(mockRouter.setTier).toHaveBeenCalledWith('default');
      expect(mockAgent.setModelTier).toHaveBeenCalledWith('default');
    } finally {
      if (prevOpenRouterKey) {
        process.env.OPENROUTER_API_KEY = prevOpenRouterKey;
      } else {
        delete process.env.OPENROUTER_API_KEY;
      }
    }
  });

  it('prints transfer result text when /transfer is invoked', async () => {
    const mockSession = {
      id: 'test',
      getHistory: () => [],
      addMessage: vi.fn(),
      clear: vi.fn(),
      replaceHistory: vi.fn(),
    };
    const onTransfer = vi.fn(() => 'Session transferred to Telegram (12345)');
    const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
    try {
      const tui = new MinimalTui({
        session: asSession(mockSession),
        modelClient: asModelClient({}),
        systemPrompt: 'test',
        onTransfer,
      });

      await minimalTuiPrivates(tui).handleCommand({ type: 'transfer', target: 'telegram' });
      expect(onTransfer).toHaveBeenCalledWith('telegram');
      expect(logSpy).toHaveBeenCalledWith('Session transferred to Telegram (12345)\n');
    } finally {
      logSpy.mockRestore();
    }
  });

  it('only renders tool activity when verbose mode is enabled', () => {
    const mockSession = {
      id: 'test',
      getHistory: () => [],
      addMessage: vi.fn(),
      clear: vi.fn(),
      replaceHistory: vi.fn(),
    };
    const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
    try {
      const tui = new MinimalTui({
        session: asSession(mockSession),
        modelClient: asModelClient({}),
        systemPrompt: 'test',
      });

      minimalTuiPrivates(tui).handleToolEvent({ type: 'start', tool: 'shell.exec', args: { command: 'ls' } });
      expect(logSpy).not.toHaveBeenCalled();

      minimalTuiPrivates(tui).handleVerboseCommand();
      minimalTuiPrivates(tui).handleToolEvent({ type: 'start', tool: 'shell.exec', args: { command: 'ls' } });
      expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Shell: Exec'));
    } finally {
      logSpy.mockRestore();
    }
  });
});

describe('MinimalTui prompt cancellation', () => {
  it('cancels an active prompt without closing the TUI', async () => {
    const mockSession = {
      id: 'test',
      getHistory: () => [],
      addMessage: vi.fn(),
      clear: vi.fn(),
      replaceHistory: vi.fn(),
    };

    const tui = new MinimalTui({
      session: asSession(mockSession),
      modelClient: asRouter({}),
      systemPrompt: 'test',
    });

    let onAnswer: ((answer: string) => void) | undefined;
    const write = vi.fn((_: string | null, key?: { ctrl?: boolean; name?: string }) => {
      if (key?.name === 'return') {
        onAnswer?.('');
      }
    });

    minimalTuiPrivates(tui).rl = {
      once: vi.fn(),
      removeListener: vi.fn(),
      question: vi.fn((_text: string, cb: (answer: string) => void) => {
        onAnswer = cb;
      }),
      write,
    };

    const promptPromise = minimalTuiPrivates(tui).prompt('Confirm? ');
    expect(minimalTuiPrivates(tui).activePromptCancel).toBeTypeOf('function');

    minimalTuiPrivates(tui).activePromptCancel?.();

    await expect(promptPromise).resolves.toBe('');
    expect(write).toHaveBeenCalledWith(null, { ctrl: true, name: 'u' });
    expect(write).toHaveBeenCalledWith(null, { name: 'return' });
    expect(minimalTuiPrivates(tui).activePromptCancel).toBeNull();
  });

  it('uses Esc to cancel active running operation', () => {
    const mockSession = {
      id: 'test',
      getHistory: () => [],
      addMessage: vi.fn(),
      clear: vi.fn(),
      replaceHistory: vi.fn(),
    };

    const tui = new MinimalTui({
      session: asSession(mockSession),
      modelClient: asRouter({}),
      systemPrompt: 'test',
    });

    const cancelRunningOperation = vi.fn();
    minimalTuiPrivates(tui).activeOperationCancel = cancelRunningOperation;

    expect(minimalTuiPrivates(tui).handleEscapeAction()).toBe(true);
    expect(cancelRunningOperation).toHaveBeenCalledOnce();
  });
});
