/**
 * @fileoverview Interval Trigger Handler Tests
 * @description Test the installation, uninstallation and firing behavior of interval triggers
 */

import { describe, it, expect, beforeEach, vi } from 'vitest';
import type { TriggerId, FlowId } from '@/entrypoints/background/record-replay-v3/domain/ids';
import type { TriggerSpecByKind } from '@/entrypoints/background/record-replay-v3/domain/triggers';
import type { TriggerFireCallback } from '@/entrypoints/background/record-replay-v3/engine/triggers/trigger-handler';
import { createIntervalTriggerHandler } from '@/entrypoints/background/record-replay-v3/engine/triggers/interval-trigger';

// ==================== Test Utilities ====================

function createMockLogger() {
  return {
    debug: vi.fn(),
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
  };
}

function createMockFireCallback(): TriggerFireCallback & { calls: Array<{ triggerId: string }> } {
  const calls: Array<{ triggerId: string }> = [];
  return {
    calls,
    onFire: vi.fn(async (triggerId) => {
      calls.push({ triggerId });
    }),
  };
}

function createIntervalTriggerSpec(
  overrides: Partial<TriggerSpecByKind<'interval'>> = {},
): TriggerSpecByKind<'interval'> {
  return {
    id: 'interval-trigger-1' as TriggerId,
    kind: 'interval',
    flowId: 'flow-1' as FlowId,
    enabled: true,
    periodMinutes: 5,
    ...overrides,
  };
}

// ==================== Mock chrome.alarms ====================

let alarmListeners: Array<(alarm: chrome.alarms.Alarm) => void> = [];
let createdAlarms: Map<string, { periodInMinutes?: number; delayInMinutes?: number }> = new Map();

function setupMockChromeAlarms() {
  alarmListeners = [];
  createdAlarms = new Map();

  const alarms = {
    create: vi.fn((name: string, info: { periodInMinutes?: number; delayInMinutes?: number }) => {
      createdAlarms.set(name, info);
      return Promise.resolve();
    }),
    clear: vi.fn((name: string) => {
      createdAlarms.delete(name);
      return Promise.resolve(true);
    }),
    getAll: vi.fn(() => {
      return Promise.resolve(
        Array.from(createdAlarms.entries()).map(([name]) => ({ name, scheduledTime: 0 })),
      );
    }),
    onAlarm: {
      addListener: vi.fn((listener: (alarm: chrome.alarms.Alarm) => void) => {
        alarmListeners.push(listener);
      }),
      removeListener: vi.fn((listener: (alarm: chrome.alarms.Alarm) => void) => {
        alarmListeners = alarmListeners.filter((l) => l !== listener);
      }),
    },
  };

  (globalThis as unknown as { chrome: { alarms: typeof alarms } }).chrome = { alarms };

  return alarms;
}

function simulateAlarmFire(name: string) {
  for (const listener of alarmListeners) {
    listener({ name, scheduledTime: Date.now() });
  }
}

// ==================== Tests ====================

describe('IntervalTriggerHandler', () => {
  let mockAlarms: ReturnType<typeof setupMockChromeAlarms>;
  let mockLogger: ReturnType<typeof createMockLogger>;
  let fireCallback: ReturnType<typeof createMockFireCallback>;

  beforeEach(() => {
    vi.clearAllMocks();
    mockAlarms = setupMockChromeAlarms();
    mockLogger = createMockLogger();
    fireCallback = createMockFireCallback();
  });

  describe('install', () => {
    it('creates repeating alarm with correct periodInMinutes', async () => {
      const handler = createIntervalTriggerHandler(fireCallback, { logger: mockLogger });
      const trigger = createIntervalTriggerSpec({ periodMinutes: 10 });

      await handler.install(trigger);

      expect(mockAlarms.create).toHaveBeenCalledWith(
        'rr_v3_interval_interval-trigger-1',
        expect.objectContaining({
          periodInMinutes: 10,
          delayInMinutes: 10,
        }),
      );
    });

    it('adds alarm listener on first install', async () => {
      const handler = createIntervalTriggerHandler(fireCallback, { logger: mockLogger });

      expect(mockAlarms.onAlarm.addListener).not.toHaveBeenCalled();

      await handler.install(createIntervalTriggerSpec());

      expect(mockAlarms.onAlarm.addListener).toHaveBeenCalledTimes(1);
    });

    it('registers trigger ID', async () => {
      const handler = createIntervalTriggerHandler(fireCallback, { logger: mockLogger });
      const trigger = createIntervalTriggerSpec();

      await handler.install(trigger);

      expect(handler.getInstalledIds()).toContain(trigger.id);
    });

    it('throws error for invalid periodMinutes', async () => {
      const handler = createIntervalTriggerHandler(fireCallback, { logger: mockLogger });

      await expect(
        handler.install(createIntervalTriggerSpec({ periodMinutes: 0 })),
      ).rejects.toThrow('periodMinutes must be >= 1');

      await expect(
        handler.install(createIntervalTriggerSpec({ periodMinutes: -5 })),
      ).rejects.toThrow('periodMinutes must be >= 1');

      await expect(
        handler.install(createIntervalTriggerSpec({ periodMinutes: NaN as number })),
      ).rejects.toThrow('periodMinutes must be a finite number');
    });
  });

  describe('uninstall', () => {
    it('clears alarm and removes trigger from installed list', async () => {
      const handler = createIntervalTriggerHandler(fireCallback, { logger: mockLogger });
      const trigger = createIntervalTriggerSpec();

      await handler.install(trigger);
      expect(handler.getInstalledIds()).toContain(trigger.id);

      await handler.uninstall(trigger.id);

      expect(mockAlarms.clear).toHaveBeenCalledWith('rr_v3_interval_interval-trigger-1');
      expect(handler.getInstalledIds()).not.toContain(trigger.id);
    });

    it('removes alarm listener when last trigger is uninstalled', async () => {
      const handler = createIntervalTriggerHandler(fireCallback, { logger: mockLogger });
      const trigger = createIntervalTriggerSpec();

      await handler.install(trigger);
      await handler.uninstall(trigger.id);

      expect(mockAlarms.onAlarm.removeListener).toHaveBeenCalled();
    });
  });

  describe('uninstallAll', () => {
    it('clears all interval alarms', async () => {
      const handler = createIntervalTriggerHandler(fireCallback, { logger: mockLogger });

      await handler.install(createIntervalTriggerSpec({ id: 'trigger-1' as TriggerId }));
      await handler.install(createIntervalTriggerSpec({ id: 'trigger-2' as TriggerId }));

      await handler.uninstallAll();

      expect(handler.getInstalledIds()).toHaveLength(0);
      expect(mockAlarms.onAlarm.removeListener).toHaveBeenCalled();
    });
  });

  describe('alarm handling', () => {
    it('fires callback when alarm triggers', async () => {
      const handler = createIntervalTriggerHandler(fireCallback, { logger: mockLogger });
      const trigger = createIntervalTriggerSpec();

      await handler.install(trigger);

      // Simulate alarm fire
      simulateAlarmFire('rr_v3_interval_interval-trigger-1');

      // Wait for async callback
      await new Promise((resolve) => setTimeout(resolve, 10));

      expect(fireCallback.onFire).toHaveBeenCalledWith(
        trigger.id,
        expect.objectContaining({
          sourceTabId: undefined,
          sourceUrl: undefined,
        }),
      );
    });

    it('ignores alarms from other handlers', async () => {
      const handler = createIntervalTriggerHandler(fireCallback, { logger: mockLogger });
      await handler.install(createIntervalTriggerSpec());

      // Simulate alarm from different handler
      simulateAlarmFire('rr_v3_once_some-other-trigger');

      await new Promise((resolve) => setTimeout(resolve, 10));

      expect(fireCallback.onFire).not.toHaveBeenCalled();
    });

    it('ignores alarms for uninstalled triggers', async () => {
      const handler = createIntervalTriggerHandler(fireCallback, { logger: mockLogger });
      const trigger = createIntervalTriggerSpec();

      await handler.install(trigger);
      await handler.uninstall(trigger.id);

      simulateAlarmFire('rr_v3_interval_interval-trigger-1');

      await new Promise((resolve) => setTimeout(resolve, 10));

      expect(fireCallback.onFire).not.toHaveBeenCalled();
    });
  });
});
