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

import {
  computeTeamWatchScope,
  markTeamEngaged,
  notifyTeamWatchScopeChanged,
  resetTeamWatchScopeForTests,
  setAliveTeamsProvider,
  setTeamWatchScopeChangeListener,
} from '../../../../src/main/services/infrastructure/teamWatchScope';

const FIVE_MIN = 5 * 60_000;

afterEach(() => {
  resetTeamWatchScopeForTests();
});

describe('teamWatchScope', () => {
  it('includes alive teams from the provider', () => {
    setAliveTeamsProvider(() => ['t-alive']);
    expect([...(computeTeamWatchScope(1000) ?? [])]).toContain('t-alive');
  });

  it('includes engaged teams within TTL and prunes after expiry', () => {
    markTeamEngaged('t-eng', 0);
    expect(computeTeamWatchScope(FIVE_MIN)?.has('t-eng')).toBe(true);
    expect(computeTeamWatchScope(FIVE_MIN + 1)?.has('t-eng')).toBe(false);
    // pruning is sticky: it stays out without re-engaging
    expect(computeTeamWatchScope(FIVE_MIN + 2)?.has('t-eng')).toBe(false);
  });

  it('unions alive and engaged teams', () => {
    setAliveTeamsProvider(() => ['a']);
    markTeamEngaged('b', 0);
    const scope = computeTeamWatchScope(1000);
    expect(scope?.has('a')).toBe(true);
    expect(scope?.has('b')).toBe(true);
  });

  it('notifies the listener only when engagement newly adds to scope', () => {
    const listener = vi.fn();
    setTeamWatchScopeChangeListener(listener);
    markTeamEngaged('x', 0);
    expect(listener).toHaveBeenCalledTimes(1);
    markTeamEngaged('x', 1000); // already in scope -> no extra churn
    expect(listener).toHaveBeenCalledTimes(1);
  });

  it('does not notify when engaging an already-alive (in-scope) team', () => {
    setAliveTeamsProvider(() => ['y']);
    const listener = vi.fn();
    setTeamWatchScopeChangeListener(listener);
    markTeamEngaged('y', 0);
    expect(listener).not.toHaveBeenCalled();
  });

  it('can notify after alive team scope changes outside engagement', () => {
    const listener = vi.fn();
    setTeamWatchScopeChangeListener(listener);
    notifyTeamWatchScopeChanged();
    expect(listener).toHaveBeenCalledTimes(1);
  });

  it('survives a throwing alive provider (watcher falls back safely)', () => {
    setAliveTeamsProvider(() => {
      throw new Error('boom');
    });
    expect(() => computeTeamWatchScope(0)).not.toThrow();
    expect(computeTeamWatchScope(0)).toBeNull();
  });

  it('notifies on engagement when alive provider fails so watcher can refresh to fallback', () => {
    const listener = vi.fn();
    setAliveTeamsProvider(() => {
      throw new Error('boom');
    });
    setTeamWatchScopeChangeListener(listener);

    markTeamEngaged('x', 0);

    expect(listener).toHaveBeenCalledTimes(1);
  });

  it('ignores empty team names', () => {
    const listener = vi.fn();
    setTeamWatchScopeChangeListener(listener);
    markTeamEngaged('', 0);
    expect(listener).not.toHaveBeenCalled();
    expect(computeTeamWatchScope(0)?.size).toBe(0);
  });
});
