import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';

// Mock dependencies before imports
vi.mock('./config.js', () => ({
  config: { PLATFORM_SERVICE_URL: 'http://localhost:4003', FEATURE_FLAGS_ENABLED: false },
}));
vi.mock('./product-config.js', () => ({
  PRODUCT_ID: 'clawcowork',
}));
vi.mock('./ipc-bridge.js', () => ({
  getIpcBridge: vi.fn(),
}));
vi.mock('./feature-flags.js', () => ({
  setFlag: vi.fn(),
}));
vi.mock('./platform-client.js', () => ({
  postAuditEvents: vi.fn(),
  postTelemetryEvents: vi.fn(),
  postUsageRecords: vi.fn(),
  pollFlags: vi.fn(),
}));

import { FlushScheduler } from './flush-scheduler.js';
import { getIpcBridge } from './ipc-bridge.js';
import { setFlag } from './feature-flags.js';
import {
  postAuditEvents,
  postTelemetryEvents,
  postUsageRecords,
  pollFlags,
} from './platform-client.js';

const mockLog = {
  info: vi.fn(),
  warn: vi.fn(),
  error: vi.fn(),
  debug: vi.fn(),
  fatal: vi.fn(),
  trace: vi.fn(),
  child: vi.fn(),
  silent: vi.fn(),
  level: 'info',
} as unknown as import('fastify').FastifyBaseLogger;

function createMockBridge(running = true) {
  return {
    isRunning: running,
    flushAudit: vi.fn().mockResolvedValue({ result: { count: 2, entries: [
      { userId: 'u1', action: 'task.submit', category: 'task', details: {} },
      { userId: 'u2', action: 'task.cancel', category: 'task', details: {} },
    ] } }),
    flushTelemetry: vi.fn().mockResolvedValue({ result: { count: 1, events: [
      { id: 'e1', name: 'task.submit', timestamp: '2026-01-01T00:00:00Z' },
    ] } }),
    flushBudget: vi.fn().mockResolvedValue({ result: { count: 1, records: [
      { userId: 'u1', date: '2026-01-01', model: 'claude-sonnet', inputTokens: 100, outputTokens: 50, costUsd: 0.01 },
    ] } }),
    updateFlags: vi.fn().mockResolvedValue({ result: { updated: 3 } }),
  };
}

beforeEach(() => {
  vi.clearAllMocks();
});

afterEach(() => {
  vi.restoreAllMocks();
});

describe('FlushScheduler', () => {
  describe('lifecycle', () => {
    it('starts and stops without errors', () => {
      const scheduler = new FlushScheduler({ log: mockLog });
      expect(scheduler.isRunning).toBe(false);

      scheduler.start();
      expect(scheduler.isRunning).toBe(true);

      scheduler.stop();
      expect(scheduler.isRunning).toBe(false);
    });

    it('start is idempotent', () => {
      const scheduler = new FlushScheduler({ log: mockLog });
      scheduler.start();
      scheduler.start(); // no-op
      expect(scheduler.isRunning).toBe(true);
      scheduler.stop();
    });
  });

  describe('flushAll', () => {
    it('skips when IPC bridge is not running', async () => {
      const bridge = createMockBridge(false);
      vi.mocked(getIpcBridge).mockReturnValue(bridge as unknown as ReturnType<typeof getIpcBridge>);

      const scheduler = new FlushScheduler({ log: mockLog });
      const result = await scheduler.flushAll();

      expect(result.skipped).toBe(true);
      expect(bridge.flushAudit).not.toHaveBeenCalled();
    });

    it('drains audit, telemetry, and budget from IPC and posts to platform-service', async () => {
      const bridge = createMockBridge(true);
      vi.mocked(getIpcBridge).mockReturnValue(bridge as unknown as ReturnType<typeof getIpcBridge>);
      vi.mocked(postAuditEvents).mockResolvedValue({ posted: 2, errors: 0 });
      vi.mocked(postTelemetryEvents).mockResolvedValue({ accepted: 1, rejected: 0 });
      vi.mocked(postUsageRecords).mockResolvedValue({ posted: 1, errors: 0 });

      const scheduler = new FlushScheduler({ log: mockLog });
      const result = await scheduler.flushAll();

      expect(result.skipped).toBe(false);
      expect(result.audit).toEqual({ drained: 2, posted: 2, errors: 0 });
      expect(result.telemetry).toEqual({ drained: 1, accepted: 1, rejected: 0 });
      expect(result.budget).toEqual({ drained: 1, posted: 1, errors: 0 });
    });

    it('handles IPC flush_audit error gracefully', async () => {
      const bridge = createMockBridge(true);
      bridge.flushAudit.mockResolvedValue({ error: { code: -32603, message: 'internal' } });
      vi.mocked(getIpcBridge).mockReturnValue(bridge as unknown as ReturnType<typeof getIpcBridge>);
      vi.mocked(postTelemetryEvents).mockResolvedValue({ accepted: 1, rejected: 0 });
      vi.mocked(postUsageRecords).mockResolvedValue({ posted: 1, errors: 0 });

      const scheduler = new FlushScheduler({ log: mockLog });
      const result = await scheduler.flushAll();

      expect(result.audit).toBeNull();
      expect(result.telemetry).not.toBeNull();
    });

    it('handles empty IPC responses', async () => {
      const bridge = createMockBridge(true);
      bridge.flushAudit.mockResolvedValue({ result: { count: 0, entries: [] } });
      bridge.flushTelemetry.mockResolvedValue({ result: { count: 0, events: [] } });
      bridge.flushBudget.mockResolvedValue({ result: { count: 0, records: [] } });
      vi.mocked(getIpcBridge).mockReturnValue(bridge as unknown as ReturnType<typeof getIpcBridge>);

      const scheduler = new FlushScheduler({ log: mockLog });
      const result = await scheduler.flushAll();

      expect(result.audit).toEqual({ drained: 0, posted: 0, errors: 0 });
      expect(postAuditEvents).not.toHaveBeenCalled();
    });
  });

  describe('pollAndSyncFlags', () => {
    it('polls flags and updates local registry + IPC bridge', async () => {
      const bridge = createMockBridge(true);
      vi.mocked(getIpcBridge).mockReturnValue(bridge as unknown as ReturnType<typeof getIpcBridge>);
      vi.mocked(pollFlags).mockResolvedValue({
        flags: { sandbox_enabled: true, computer_use_enabled: true, wasm_plugins_enabled: false },
        productId: 'clawcowork',
      });

      const scheduler = new FlushScheduler({ log: mockLog });
      const result = await scheduler.pollAndSyncFlags();

      expect(result).toEqual({ updated: 3 });
      expect(setFlag).toHaveBeenCalledTimes(3);
      expect(setFlag).toHaveBeenCalledWith('sandbox_enabled', true);
      expect(setFlag).toHaveBeenCalledWith('computer_use_enabled', true);
      expect(bridge.updateFlags).toHaveBeenCalledWith(
        { sandbox_enabled: true, computer_use_enabled: true, wasm_plugins_enabled: false },
        { userId: 'system', role: 'admin' },
      );
    });

    it('handles poll failure gracefully', async () => {
      vi.mocked(pollFlags).mockRejectedValue(new Error('network down'));

      const scheduler = new FlushScheduler({ log: mockLog });
      const result = await scheduler.pollAndSyncFlags();

      expect(result).toBeNull();
      expect(mockLog.error).toHaveBeenCalled();
    });

    it('skips IPC update when bridge is not running', async () => {
      const bridge = createMockBridge(false);
      vi.mocked(getIpcBridge).mockReturnValue(bridge as unknown as ReturnType<typeof getIpcBridge>);
      vi.mocked(pollFlags).mockResolvedValue({
        flags: { sandbox_enabled: true },
        productId: 'clawcowork',
      });

      const scheduler = new FlushScheduler({ log: mockLog });
      await scheduler.pollAndSyncFlags();

      expect(setFlag).toHaveBeenCalledWith('sandbox_enabled', true);
      expect(bridge.updateFlags).not.toHaveBeenCalled();
    });
  });
});
