import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createBackupCommand } from '../../src/commands/backup.js';

const mockClient = {
  get: vi.fn(),
  post: vi.fn(),
  put: vi.fn(),
  delete: vi.fn(),
};

const log = vi.fn();

function makeCmd() {
  return createBackupCommand({ client: mockClient as never, log });
}

describe('backup command', () => {
  beforeEach(() => {
    vi.resetAllMocks();
  });

  it('creates backup command', () => {
    expect(makeCmd().name()).toBe('backup');
  });

  it('shows status when enabled', async () => {
    mockClient.get.mockResolvedValue({
      enabled: true,
      repoUrl: 'ssh://git@10.0.0.194:2222/michal/mcp-backup.git',
      gitReachable: true,
      lastSyncAt: new Date().toISOString(),
      lastPushAt: null,
      lastError: null,
      pendingCount: 0,
    });

    await makeCmd().parseAsync([], { from: 'user' });

    expect(mockClient.get).toHaveBeenCalledWith('/api/v1/backup/status');
    expect(log).toHaveBeenCalledWith(expect.stringContaining('ssh://git@10.0.0.194:2222/michal/mcp-backup.git'));
    expect(log).toHaveBeenCalledWith(expect.stringContaining('synced'));
  });

  it('shows disabled when not configured', async () => {
    mockClient.get.mockResolvedValue({
      enabled: false,
      repoUrl: null,
      gitReachable: false,
      lastSyncAt: null,
      lastPushAt: null,
      lastError: null,
      pendingCount: 0,
    });

    await makeCmd().parseAsync([], { from: 'user' });

    expect(log).toHaveBeenCalledWith(expect.stringContaining('disabled'));
  });

  it('shows pending count', async () => {
    mockClient.get.mockResolvedValue({
      enabled: true,
      repoUrl: 'ssh://git@host/repo.git',
      gitReachable: true,
      lastSyncAt: null,
      lastPushAt: null,
      lastError: null,
      pendingCount: 5,
    });

    await makeCmd().parseAsync([], { from: 'user' });

    expect(log).toHaveBeenCalledWith(expect.stringContaining('5 changes pending'));
  });

  it('shows SSH public key in status when enabled', async () => {
    mockClient.get.mockResolvedValue({
      enabled: true,
      repoUrl: 'ssh://git@host/repo.git',
      publicKey: 'ssh-ed25519 AAAA... mcpd@mcpctl.local',
      gitReachable: true,
      lastSyncAt: null,
      lastPushAt: null,
      lastError: null,
      pendingCount: 0,
    });

    await makeCmd().parseAsync([], { from: 'user' });

    expect(log).toHaveBeenCalledWith(expect.stringContaining('ssh-ed25519 AAAA... mcpd@mcpctl.local'));
  });

  it('shows setup instructions when disabled', async () => {
    mockClient.get.mockResolvedValue({
      enabled: false,
      repoUrl: null,
      publicKey: null,
      gitReachable: false,
      lastSyncAt: null,
      lastPushAt: null,
      lastError: null,
      pendingCount: 0,
    });

    await makeCmd().parseAsync([], { from: 'user' });

    expect(log).toHaveBeenCalledWith(expect.stringContaining('mcpctl create secret backup-ssh'));
  });

  it('shows commit log', async () => {
    mockClient.get.mockResolvedValue({
      entries: [
        { hash: 'abc1234567890', date: '2026-03-08T10:00:00Z', author: 'mcpd <mcpd@mcpctl.local>', message: 'Update server grafana', manual: false },
        { hash: 'def4567890123', date: '2026-03-07T09:00:00Z', author: 'Michal <michal@test.com>', message: 'Manual fix', manual: true },
      ],
    });

    await makeCmd().parseAsync(['log'], { from: 'user' });

    expect(mockClient.get).toHaveBeenCalledWith('/api/v1/backup/log?limit=20');
    expect(log).toHaveBeenCalledWith(expect.stringContaining('COMMIT'));
    expect(log).toHaveBeenCalledWith(expect.stringContaining('abc1234'));
    expect(log).toHaveBeenCalledWith(expect.stringContaining('[manual]'));
  });
});

describe('backup restore subcommands', () => {
  beforeEach(() => {
    vi.resetAllMocks();
  });

  it('lists restore points', async () => {
    mockClient.get.mockResolvedValue({
      entries: [
        { hash: 'abc1234567890', date: '2026-03-08T10:00:00Z', author: 'mcpd <mcpd@mcpctl.local>', message: 'Sync' },
      ],
    });

    await makeCmd().parseAsync(['restore', 'list'], { from: 'user' });

    expect(mockClient.get).toHaveBeenCalledWith('/api/v1/backup/log?limit=30');
    expect(log).toHaveBeenCalledWith(expect.stringContaining('abc1234'));
  });

  it('shows restore diff preview', async () => {
    mockClient.post.mockResolvedValue({
      targetCommit: 'abc1234567890',
      targetDate: '2026-03-08T10:00:00Z',
      targetMessage: 'Snapshot',
      added: ['servers/new.yaml'],
      removed: ['servers/old.yaml'],
      modified: ['projects/default.yaml'],
    });

    await makeCmd().parseAsync(['restore', 'diff', 'abc1234'], { from: 'user' });

    expect(mockClient.post).toHaveBeenCalledWith('/api/v1/backup/restore/preview', { commit: 'abc1234' });
    expect(log).toHaveBeenCalledWith(expect.stringContaining('+ servers/new.yaml'));
    expect(log).toHaveBeenCalledWith(expect.stringContaining('- servers/old.yaml'));
    expect(log).toHaveBeenCalledWith(expect.stringContaining('~ projects/default.yaml'));
  });

  it('requires --force for restore', async () => {
    mockClient.post.mockResolvedValue({
      targetCommit: 'abc1234567890',
      targetDate: '2026-03-08T10:00:00Z',
      targetMessage: 'Snapshot',
      added: ['servers/new.yaml'],
      removed: [],
      modified: [],
    });

    await makeCmd().parseAsync(['restore', 'to', 'abc1234'], { from: 'user' });

    expect(mockClient.post).toHaveBeenCalledWith('/api/v1/backup/restore/preview', { commit: 'abc1234' });
    expect(mockClient.post).not.toHaveBeenCalledWith('/api/v1/backup/restore', expect.anything());
    expect(log).toHaveBeenCalledWith(expect.stringContaining('--force'));
  });

  it('executes restore with --force', async () => {
    mockClient.post
      .mockResolvedValueOnce({
        targetCommit: 'abc1234567890',
        targetDate: '2026-03-08T10:00:00Z',
        targetMessage: 'Snapshot',
        added: ['servers/new.yaml'],
        removed: [],
        modified: [],
      })
      .mockResolvedValueOnce({
        branchName: 'timeline/20260308-100000',
        applied: 1,
        deleted: 0,
        errors: [],
      });

    await makeCmd().parseAsync(['restore', 'to', 'abc1234', '--force'], { from: 'user' });

    expect(mockClient.post).toHaveBeenCalledWith('/api/v1/backup/restore', { commit: 'abc1234' });
    expect(log).toHaveBeenCalledWith(expect.stringContaining('1 applied'));
    expect(log).toHaveBeenCalledWith(expect.stringContaining('timeline/20260308-100000'));
  });

  it('reports restore errors', async () => {
    mockClient.post
      .mockResolvedValueOnce({
        targetCommit: 'abc1234567890',
        targetDate: '2026-03-08T10:00:00Z',
        targetMessage: 'Snapshot',
        added: [],
        removed: [],
        modified: ['servers/broken.yaml'],
      })
      .mockResolvedValueOnce({
        branchName: 'timeline/20260308-100000',
        applied: 0,
        deleted: 0,
        errors: ['Failed to apply servers/broken.yaml: invalid YAML'],
      });

    await makeCmd().parseAsync(['restore', 'to', 'abc1234', '--force'], { from: 'user' });

    expect(log).toHaveBeenCalledWith('Errors:');
    expect(log).toHaveBeenCalledWith(expect.stringContaining('invalid YAML'));
  });
});
