import { TwitchAuthService, TwitchJwtPayload } from './twitch-auth.service';

function makeJwt(payload: Partial<TwitchJwtPayload> = {}): string {
  const full: TwitchJwtPayload = {
    exp: Math.floor(Date.now() / 1000) + 3600,
    opaque_user_id: 'U12345678',
    user_id: '12345678',
    channel_id: '987654321',
    role: 'viewer',
    ...payload,
  };
  const encoded = btoa(JSON.stringify(full))
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '');
  return `eyJhbGciOiJIUzI1NiJ9.${encoded}.sig`;
}

function makeTwitchAuth(payload: Partial<TwitchJwtPayload> = {}): TwitchAuth {
  return {
    channelId: payload.channel_id ?? '987654321',
    clientId: 'test_client',
    token: makeJwt(payload),
    userId: payload.opaque_user_id ?? 'U12345678',
  };
}

describe('TwitchAuthService', () => {
  let service: TwitchAuthService;
  let extCallbacks: {
    onAuthorized?: (auth: TwitchAuth) => void;
    onContext?: (ctx: TwitchContext, changed: (keyof TwitchContext)[]) => void;
    onVisibilityChanged?: (visible: boolean, ctx: TwitchContext) => void;
  };

  beforeEach(() => {
    extCallbacks = {};
    (window as Window & { Twitch?: unknown }).Twitch = {
      ext: {
        onAuthorized: (cb: (auth: TwitchAuth) => void) => { extCallbacks.onAuthorized = cb; },
        onContext: (cb: (ctx: TwitchContext, changed: (keyof TwitchContext)[]) => void) => { extCallbacks.onContext = cb; },
        onVisibilityChanged: (cb: (visible: boolean, ctx: TwitchContext) => void) => { extCallbacks.onVisibilityChanged = cb; },
        listen: () => undefined,
        unlisten: () => undefined,
        send: () => undefined,
      },
    };

    service = new TwitchAuthService();
    service.init();
  });

  afterEach(() => {
    delete (window as Window & { Twitch?: unknown }).Twitch;
  });

  it('exposes null auth before onAuthorized fires', () => {
    expect(service.auth()).toBeNull();
    expect(service.jwtPayload()).toBeNull();
    expect(service.isLoggedIn()).toBe(false);
    expect(service.channelId()).toBeNull();
  });

  it('sets auth and decoded payload when onAuthorized fires', () => {
    extCallbacks.onAuthorized!(makeTwitchAuth());

    expect(service.auth()).not.toBeNull();
    expect(service.jwtPayload()?.opaque_user_id).toBe('U12345678');
    expect(service.jwtPayload()?.channel_id).toBe('987654321');
    expect(service.channelId()).toBe('987654321');
  });

  it('reports isLoggedIn true for U-prefixed opaque_user_id', () => {
    extCallbacks.onAuthorized!(makeTwitchAuth({ opaque_user_id: 'U99999999' }));
    expect(service.isLoggedIn()).toBe(true);
  });

  it('reports isLoggedIn false for A-prefixed opaque_user_id', () => {
    extCallbacks.onAuthorized!(makeTwitchAuth({ opaque_user_id: 'Aanonymous' }));
    expect(service.isLoggedIn()).toBe(false);
  });

  it('defaults isVisible to true', () => {
    expect(service.isVisible()).toBe(true);
  });

  it('updates isVisible when onVisibilityChanged fires', () => {
    const ctx = {} as TwitchContext;
    extCallbacks.onVisibilityChanged!(false, ctx);
    expect(service.isVisible()).toBe(false);

    extCallbacks.onVisibilityChanged!(true, ctx);
    expect(service.isVisible()).toBe(true);
  });

  it('updates context when onContext fires', () => {
    const ctx = { game: 'test-game' } as TwitchContext;
    extCallbacks.onContext!(ctx, ['game']);
    expect(service.context()?.game).toBe('test-game');
  });

  it('updates context from onVisibilityChanged', () => {
    const ctx = { isFullScreen: true } as TwitchContext;
    extCallbacks.onVisibilityChanged!(false, ctx);
    expect(service.context()?.isFullScreen).toBe(true);
  });
});
