import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { mkdtempSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { createStatusCommand } from '../../src/commands/status.js';
import type { StatusCommandDeps } from '../../src/commands/status.js';
import { saveConfig, DEFAULT_CONFIG } from '../../src/config/index.js';
import { saveCredentials } from '../../src/auth/index.js';

let tempDir: string;
let output: string[];
let written: string[];

function log(...args: string[]) {
  output.push(args.join(' '));
}

function write(text: string) {
  written.push(text);
}

function baseDeps(overrides?: Partial<StatusCommandDeps>): Partial<StatusCommandDeps> {
  return {
    configDeps: { configDir: tempDir },
    credentialsDeps: { configDir: tempDir },
    log,
    write,
    checkHealth: async () => true,
    fetchProviders: async () => null,
    isTTY: false,
    ...overrides,
  };
}

beforeEach(() => {
  tempDir = mkdtempSync(join(tmpdir(), 'mcpctl-status-test-'));
  output = [];
  written = [];
});

afterEach(() => {
  rmSync(tempDir, { recursive: true, force: true });
});

describe('status command', () => {
  it('shows status in table format', async () => {
    const cmd = createStatusCommand(baseDeps());
    await cmd.parseAsync([], { from: 'user' });
    const out = output.join('\n');
    expect(out).toContain('mcpctl v');
    expect(out).toContain('mcplocal:');
    expect(out).toContain('mcpd:');
    expect(out).toContain('connected');
  });

  it('shows unreachable when daemons are down', async () => {
    const cmd = createStatusCommand(baseDeps({ checkHealth: async () => false }));
    await cmd.parseAsync([], { from: 'user' });
    expect(output.join('\n')).toContain('unreachable');
  });

  it('shows not logged in when no credentials', async () => {
    const cmd = createStatusCommand(baseDeps());
    await cmd.parseAsync([], { from: 'user' });
    expect(output.join('\n')).toContain('not logged in');
  });

  it('shows logged in user when credentials exist', async () => {
    saveCredentials({ token: 'tok', mcpdUrl: 'http://x:3100', user: 'alice@example.com' }, { configDir: tempDir });
    const cmd = createStatusCommand(baseDeps());
    await cmd.parseAsync([], { from: 'user' });
    expect(output.join('\n')).toContain('logged in as alice@example.com');
  });

  it('shows status in JSON format', async () => {
    const cmd = createStatusCommand(baseDeps());
    await cmd.parseAsync(['-o', 'json'], { from: 'user' });
    const parsed = JSON.parse(output[0]) as Record<string, unknown>;
    expect(parsed['version']).toBe('0.0.1');
    expect(parsed['mcplocalReachable']).toBe(true);
    expect(parsed['mcpdReachable']).toBe(true);
  });

  it('shows status in YAML format', async () => {
    const cmd = createStatusCommand(baseDeps({ checkHealth: async () => false }));
    await cmd.parseAsync(['-o', 'yaml'], { from: 'user' });
    expect(output[0]).toContain('mcplocalReachable: false');
  });

  it('checks correct URLs from config', async () => {
    saveConfig({ ...DEFAULT_CONFIG, mcplocalUrl: 'http://local:3200', mcpdUrl: 'http://remote:3100' }, { configDir: tempDir });
    const checkedUrls: string[] = [];
    const cmd = createStatusCommand(baseDeps({
      checkHealth: async (url) => {
        checkedUrls.push(url);
        return false;
      },
    }));
    await cmd.parseAsync([], { from: 'user' });
    expect(checkedUrls).toContain('http://local:3200');
    expect(checkedUrls).toContain('http://remote:3100');
  });

  it('shows registries from config', async () => {
    saveConfig({ ...DEFAULT_CONFIG, registries: ['official'] }, { configDir: tempDir });
    const cmd = createStatusCommand(baseDeps());
    await cmd.parseAsync([], { from: 'user' });
    expect(output.join('\n')).toContain('official');
    expect(output.join('\n')).not.toContain('glama');
  });

  it('shows LLM not configured hint when no LLM is set', async () => {
    const cmd = createStatusCommand(baseDeps());
    await cmd.parseAsync([], { from: 'user' });
    const out = output.join('\n');
    expect(out).toContain('LLM:');
    expect(out).toContain('not configured');
    expect(out).toContain('mcpctl config setup');
  });

  it('shows green check when LLM is healthy (non-TTY)', async () => {
    saveConfig({ ...DEFAULT_CONFIG, llm: { provider: 'anthropic', model: 'claude-haiku-3-5-20241022' } }, { configDir: tempDir });
    const cmd = createStatusCommand(baseDeps({ checkLlm: async () => 'ok' }));
    await cmd.parseAsync([], { from: 'user' });
    const out = output.join('\n');
    expect(out).toContain('anthropic / claude-haiku-3-5-20241022');
    expect(out).toContain('✓ ok');
  });

  it('shows red cross when LLM check fails (non-TTY)', async () => {
    saveConfig({ ...DEFAULT_CONFIG, llm: { provider: 'gemini-cli', model: 'gemini-2.5-flash' } }, { configDir: tempDir });
    const cmd = createStatusCommand(baseDeps({ checkLlm: async () => 'not authenticated' }));
    await cmd.parseAsync([], { from: 'user' });
    const out = output.join('\n');
    expect(out).toContain('✗ not authenticated');
  });

  it('shows error message from mcplocal', async () => {
    saveConfig({ ...DEFAULT_CONFIG, llm: { provider: 'gemini-cli', model: 'gemini-2.5-flash' } }, { configDir: tempDir });
    const cmd = createStatusCommand(baseDeps({ checkLlm: async () => 'binary not found' }));
    await cmd.parseAsync([], { from: 'user' });
    expect(output.join('\n')).toContain('✗ binary not found');
  });

  it('queries mcplocal URL for LLM health', async () => {
    saveConfig({ ...DEFAULT_CONFIG, mcplocalUrl: 'http://custom:9999', llm: { provider: 'gemini-cli', model: 'gemini-2.5-flash' } }, { configDir: tempDir });
    let queriedUrl = '';
    const cmd = createStatusCommand(baseDeps({
      checkLlm: async (url) => { queriedUrl = url; return 'ok'; },
    }));
    await cmd.parseAsync([], { from: 'user' });
    expect(queriedUrl).toBe('http://custom:9999');
  });

  it('uses spinner on TTY and writes final result', async () => {
    saveConfig({ ...DEFAULT_CONFIG, llm: { provider: 'gemini-cli', model: 'gemini-2.5-flash' } }, { configDir: tempDir });
    const cmd = createStatusCommand(baseDeps({
      isTTY: true,
      checkLlm: async () => 'ok',
    }));
    await cmd.parseAsync([], { from: 'user' });
    // On TTY, the final LLM line goes through write(), not log()
    const finalWrite = written[written.length - 1];
    expect(finalWrite).toContain('gemini-cli / gemini-2.5-flash');
    expect(finalWrite).toContain('✓ ok');
  });

  it('uses spinner on TTY and shows failure', async () => {
    saveConfig({ ...DEFAULT_CONFIG, llm: { provider: 'gemini-cli', model: 'gemini-2.5-flash' } }, { configDir: tempDir });
    const cmd = createStatusCommand(baseDeps({
      isTTY: true,
      checkLlm: async () => 'not authenticated',
    }));
    await cmd.parseAsync([], { from: 'user' });
    const finalWrite = written[written.length - 1];
    expect(finalWrite).toContain('✗ not authenticated');
  });

  it('shows not configured when LLM provider is none', async () => {
    saveConfig({ ...DEFAULT_CONFIG, llm: { provider: 'none' } }, { configDir: tempDir });
    const cmd = createStatusCommand(baseDeps());
    await cmd.parseAsync([], { from: 'user' });
    expect(output.join('\n')).toContain('not configured');
  });

  it('includes llm and llmStatus in JSON output', async () => {
    saveConfig({ ...DEFAULT_CONFIG, llm: { provider: 'gemini-cli', model: 'gemini-2.5-flash' } }, { configDir: tempDir });
    const cmd = createStatusCommand(baseDeps({ checkLlm: async () => 'ok' }));
    await cmd.parseAsync(['-o', 'json'], { from: 'user' });
    const parsed = JSON.parse(output[0]) as Record<string, unknown>;
    expect(parsed['llm']).toBe('gemini-cli / gemini-2.5-flash');
    expect(parsed['llmStatus']).toBe('ok');
  });

  it('includes null llm in JSON output when not configured', async () => {
    const cmd = createStatusCommand(baseDeps());
    await cmd.parseAsync(['-o', 'json'], { from: 'user' });
    const parsed = JSON.parse(output[0]) as Record<string, unknown>;
    expect(parsed['llm']).toBeNull();
    expect(parsed['llmStatus']).toBeNull();
  });
});
