import assert from 'node:assert/strict';
import test from 'node:test';
import {
  clearJellyfinAuthSessionAndRefreshTray,
  isJellyfinConfiguredForTray,
  toggleJellyfinDiscoveryFromTray,
} from './jellyfin-tray-discovery';

test('detects Jellyfin tray configuration when Jellyfin has a server URL', () => {
  assert.equal(
    isJellyfinConfiguredForTray({
      getResolvedJellyfinConfig: () => ({
        enabled: true,
        serverUrl: 'http://server:8096',
        accessToken: 'token',
        userId: 'user',
      }),
    }),
    true,
  );

  assert.equal(
    isJellyfinConfiguredForTray({
      getResolvedJellyfinConfig: () => ({
        enabled: true,
        serverUrl: 'http://server:8096',
      }),
    }),
    true,
  );

  assert.equal(
    isJellyfinConfiguredForTray({
      getResolvedJellyfinConfig: () => ({
        enabled: false,
        serverUrl: 'http://server:8096',
        accessToken: 'token',
        userId: 'user',
      }),
    }),
    false,
  );

  assert.equal(
    isJellyfinConfiguredForTray({
      getResolvedJellyfinConfig: () => ({
        enabled: true,
        serverUrl: '',
        accessToken: 'token',
        userId: 'user',
      }),
    }),
    false,
  );
});

test('clears stored auth, stops active discovery, and refreshes tray', () => {
  const calls: string[] = [];

  clearJellyfinAuthSessionAndRefreshTray({
    clearStoredSession: () => calls.push('clear'),
    getRemoteSession: () => ({ advertiseNow: async () => true }),
    stopRemoteSession: () => calls.push('stop'),
    refreshTrayMenu: () => calls.push('refresh'),
    logger: {
      info: (message) => calls.push(`info:${message}`),
      warn: (message) => calls.push(`warn:${message}`),
      error: (message) => calls.push(`error:${message}`),
    },
  });

  assert.deepEqual(calls, ['clear', 'stop', 'refresh']);
});

test('clear auth still refreshes tray when clear or stop throws', () => {
  const calls: string[] = [];

  clearJellyfinAuthSessionAndRefreshTray({
    clearStoredSession: () => {
      throw new Error('clear failed');
    },
    getRemoteSession: () => ({ advertiseNow: async () => true }),
    stopRemoteSession: () => {
      throw new Error('stop failed');
    },
    refreshTrayMenu: () => calls.push('refresh'),
    logger: {
      info: (message) => calls.push(`info:${message}`),
      warn: (message) => calls.push(`warn:${message}`),
      error: (message) => calls.push(`error:${message}`),
    },
  });

  assert.deepEqual(calls, [
    'error:Failed to clear Jellyfin auth session.',
    'error:Failed to stop Jellyfin discovery while clearing auth session.',
    'refresh',
  ]);
});

test('starts explicit discovery and advertises cast target from tray', async () => {
  const calls: string[] = [];
  let session: { advertiseNow: () => Promise<boolean> } | null = null;

  await toggleJellyfinDiscoveryFromTray({
    getRemoteSession: () => session,
    stopRemoteSession: () => calls.push('stop'),
    startRemoteSession: async (options) => {
      assert.deepEqual(options, { explicit: true });
      calls.push('start');
      session = {
        advertiseNow: async () => {
          calls.push('advertise');
          return true;
        },
      };
    },
    refreshTrayMenu: () => calls.push('refresh'),
    logger: {
      info: (message) => calls.push(`info:${message}`),
      warn: (message) => calls.push(`warn:${message}`),
      error: (message) => calls.push(`error:${message}`),
    },
    showMpvOsd: (message) => calls.push(`osd:${message}`),
  });

  assert.deepEqual(calls, [
    'start',
    'advertise',
    'info:Jellyfin discovery started; cast target is visible in server sessions.',
    'osd:Jellyfin discovery started',
    'refresh',
  ]);
});

test('starts explicit discovery and reports pending visibility from tray', async () => {
  const calls: string[] = [];
  let session: { advertiseNow: () => Promise<boolean> } | null = null;

  await toggleJellyfinDiscoveryFromTray({
    getRemoteSession: () => session,
    stopRemoteSession: () => calls.push('stop'),
    startRemoteSession: async (options) => {
      assert.deepEqual(options, { explicit: true });
      calls.push('start');
      session = {
        advertiseNow: async () => {
          calls.push('advertise');
          return false;
        },
      };
    },
    refreshTrayMenu: () => calls.push('refresh'),
    logger: {
      info: (message) => calls.push(`info:${message}`),
      warn: (message) => calls.push(`warn:${message}`),
      error: (message) => calls.push(`error:${message}`),
    },
    showMpvOsd: (message) => calls.push(`osd:${message}`),
  });

  assert.deepEqual(calls, [
    'start',
    'advertise',
    'warn:Jellyfin discovery started, but cast target is not visible yet.',
    'osd:Jellyfin discovery started; waiting for visibility',
    'refresh',
  ]);
});

test('stops active discovery from tray', async () => {
  const calls: string[] = [];

  await toggleJellyfinDiscoveryFromTray({
    getRemoteSession: () => ({ advertiseNow: async () => true }),
    stopRemoteSession: () => calls.push('stop'),
    startRemoteSession: async () => {
      calls.push('start');
    },
    refreshTrayMenu: () => calls.push('refresh'),
    logger: {
      info: (message) => calls.push(`info:${message}`),
      warn: (message) => calls.push(`warn:${message}`),
      error: (message) => calls.push(`error:${message}`),
    },
    showMpvOsd: (message) => calls.push(`osd:${message}`),
  });

  assert.deepEqual(calls, [
    'stop',
    'info:Jellyfin discovery stopped.',
    'osd:Jellyfin discovery stopped',
    'refresh',
  ]);
});

test('uses checked tray state to start discovery instead of blind toggling', async () => {
  const calls: string[] = [];
  let session: { advertiseNow: () => Promise<boolean> } | null = null;

  await toggleJellyfinDiscoveryFromTray(
    {
      getRemoteSession: () => session,
      stopRemoteSession: () => calls.push('stop'),
      startRemoteSession: async (options) => {
        assert.deepEqual(options, { explicit: true });
        calls.push('start');
        session = {
          advertiseNow: async () => {
            calls.push('advertise');
            return true;
          },
        };
      },
      refreshTrayMenu: () => calls.push('refresh'),
      logger: {
        info: (message) => calls.push(`info:${message}`),
        warn: (message) => calls.push(`warn:${message}`),
        error: (message) => calls.push(`error:${message}`),
      },
      showMpvOsd: (message) => calls.push(`osd:${message}`),
    },
    { desiredActive: true },
  );

  assert.deepEqual(calls, [
    'start',
    'advertise',
    'info:Jellyfin discovery started; cast target is visible in server sessions.',
    'osd:Jellyfin discovery started',
    'refresh',
  ]);
});

test('uses unchecked tray state to stop discovery without visibility probing', async () => {
  const calls: string[] = [];

  await toggleJellyfinDiscoveryFromTray(
    {
      getRemoteSession: () => ({
        advertiseNow: async () => {
          calls.push('advertise');
          return true;
        },
      }),
      stopRemoteSession: () => calls.push('stop'),
      startRemoteSession: async () => {
        calls.push('start');
      },
      refreshTrayMenu: () => calls.push('refresh'),
      logger: {
        info: (message) => calls.push(`info:${message}`),
        warn: (message) => calls.push(`warn:${message}`),
        error: (message) => calls.push(`error:${message}`),
      },
      showMpvOsd: (message) => calls.push(`osd:${message}`),
    },
    { desiredActive: false },
  );

  assert.deepEqual(calls, [
    'stop',
    'info:Jellyfin discovery stopped.',
    'osd:Jellyfin discovery stopped',
    'refresh',
  ]);
});

test('restarts active discovery when current session is not visible', async () => {
  const calls: string[] = [];
  let session: { advertiseNow: () => Promise<boolean> } | null = {
    advertiseNow: async () => {
      calls.push('advertise-stale');
      return false;
    },
  };

  await toggleJellyfinDiscoveryFromTray({
    getRemoteSession: () => session,
    stopRemoteSession: () => {
      calls.push('stop');
      session = null;
    },
    startRemoteSession: async (options) => {
      assert.deepEqual(options, { explicit: true });
      calls.push('start');
      session = {
        advertiseNow: async () => {
          calls.push('advertise-fresh');
          return true;
        },
      };
    },
    refreshTrayMenu: () => calls.push('refresh'),
    logger: {
      info: (message) => calls.push(`info:${message}`),
      warn: (message) => calls.push(`warn:${message}`),
      error: (message) => calls.push(`error:${message}`),
    },
    showMpvOsd: (message) => calls.push(`osd:${message}`),
  });

  assert.deepEqual(calls, [
    'advertise-stale',
    'warn:Jellyfin discovery was active but not visible; restarting.',
    'stop',
    'start',
    'advertise-fresh',
    'info:Jellyfin discovery started; cast target is visible in server sessions.',
    'osd:Jellyfin discovery started',
    'refresh',
  ]);
});

test('warns and refreshes tray when explicit discovery cannot create a session', async () => {
  const calls: string[] = [];

  await toggleJellyfinDiscoveryFromTray({
    getRemoteSession: () => null,
    stopRemoteSession: () => calls.push('stop'),
    startRemoteSession: async () => {
      calls.push('start');
    },
    refreshTrayMenu: () => calls.push('refresh'),
    logger: {
      info: (message) => calls.push(`info:${message}`),
      warn: (message) => calls.push(`warn:${message}`),
      error: (message) => calls.push(`error:${message}`),
    },
    showMpvOsd: (message) => calls.push(`osd:${message}`),
  });

  assert.deepEqual(calls, [
    'start',
    'warn:Jellyfin discovery could not start. Configure Jellyfin first.',
    'osd:Jellyfin discovery unavailable',
    'refresh',
  ]);
});

test('reports discovery toggle failures and still refreshes tray', async () => {
  const calls: string[] = [];
  const error = new Error('boom');

  await toggleJellyfinDiscoveryFromTray({
    getRemoteSession: () => null,
    stopRemoteSession: () => calls.push('stop'),
    startRemoteSession: async () => {
      throw error;
    },
    refreshTrayMenu: () => calls.push('refresh'),
    logger: {
      info: (message) => calls.push(`info:${message}`),
      warn: (message) => calls.push(`warn:${message}`),
      error: (message, actualError) => {
        calls.push(`error:${message}`);
        assert.equal(actualError, error);
      },
    },
    showMpvOsd: (message) => calls.push(`osd:${message}`),
  });

  assert.deepEqual(calls, [
    'error:Failed to toggle Jellyfin discovery.',
    'osd:Jellyfin discovery failed',
    'refresh',
  ]);
});
