import { provideHttpClient } from '@angular/common/http';
import {
  HttpTestingController,
  provideHttpClientTesting,
} from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { EBS_BASE_URL } from '../ebs/ebs-api.service';
import { TwitchAuthService } from '../twitch/twitch-auth.service';
import { MissionStateStore } from './mission-state.store';

const BASE = 'https://test.local';
const STATE_URL = `${BASE}/missions/state`;

function makeJwt(): string {
  const payload = btoa(
    JSON.stringify({ opaque_user_id: 'U123', channel_id: 'ch1', role: 'viewer', exp: 9999999999 })
  ).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
  return `eyJhbGciOiJIUzI1NiJ9.${payload}.sig`;
}

describe('MissionStateStore', () => {
  let store: MissionStateStore;
  let authService: TwitchAuthService;
  let controller: HttpTestingController;
  let visibilityCallback: ((visible: boolean, ctx: TwitchContext) => void) | undefined;
  let pubsubCallbacks: Record<string, (t: string, ct: string, msg: string) => void>;

  function mountTwitchExt(alreadyAuthorized = true) {
    const jwt = makeJwt();
    pubsubCallbacks = {};
    (window as Window & { Twitch?: unknown }).Twitch = {
      ext: {
        onAuthorized: (cb: (a: TwitchAuth) => void) => {
          if (alreadyAuthorized) {
            cb({ channelId: 'ch1', clientId: 'c1', token: jwt, userId: 'U123' });
          }
        },
        onContext: () => undefined,
        onVisibilityChanged: (cb: (v: boolean, ctx: TwitchContext) => void) => {
          visibilityCallback = cb;
        },
        listen: (target: string, cb: (t: string, ct: string, msg: string) => void) => {
          pubsubCallbacks[target] = cb;
        },
        unlisten: () => undefined,
        send: () => undefined,
      },
    };
  }

  function removeTwitchExt() {
    delete (window as Window & { Twitch?: unknown }).Twitch;
  }

  beforeEach(() => {
    visibilityCallback = undefined;
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
        { provide: EBS_BASE_URL, useValue: BASE },
      ],
    });
    store = TestBed.inject(MissionStateStore);
    authService = TestBed.inject(TwitchAuthService);
    controller = TestBed.inject(HttpTestingController);
  });

  afterEach(() => {
    controller.verify();
    removeTwitchExt();
  });

  describe('init', () => {
    it('does not fetch state when not yet authorized', () => {
      mountTwitchExt(false);
      authService.init();
      store.init();

      controller.expectNone(STATE_URL);
    });

    it('fetches state immediately when authorized', () => {
      mountTwitchExt();
      authService.init();
      store.init();

      controller.expectOne(STATE_URL).flush(null);
      expect(store.state()).toBeNull();
    });

    it('sets loading true during fetch and false after', () => {
      mountTwitchExt();
      authService.init();
      store.init();

      expect(store.loading()).toBe(true);
      controller.expectOne(STATE_URL).flush(null);
      expect(store.loading()).toBe(false);
    });

    it('sets error signal when request fails', () => {
      mountTwitchExt();
      authService.init();
      store.init();

      controller.expectOne(STATE_URL).flush('server error', {
        status: 500,
        statusText: 'Internal Server Error',
      });

      expect(store.error()).toBeTruthy();
      expect(store.loading()).toBe(false);
    });
  });

  describe('PubSub', () => {
    it('re-fetches when a broadcast arrives while visible', () => {
      mountTwitchExt();
      authService.init();
      store.init();
      controller.expectOne(STATE_URL).flush(null);

      pubsubCallbacks['broadcast']('broadcast', 'json', '{}');
      controller.expectOne(STATE_URL).flush(null);
    });

    it('ignores broadcast when overlay is hidden', () => {
      mountTwitchExt();
      authService.init();
      store.init();
      controller.expectOne(STATE_URL).flush(null);

      visibilityCallback!(false, {} as TwitchContext);
      pubsubCallbacks['broadcast']('broadcast', 'json', '{}');

      controller.expectNone(STATE_URL);
    });
  });

  describe('refresh', () => {
    it('re-fetches state when called', () => {
      mountTwitchExt();
      authService.init();
      store.init();
      controller.expectOne(STATE_URL).flush(null);

      store.refresh();
      controller.expectOne(STATE_URL).flush(null);
    });

    it('is a no-op when not yet authorized', () => {
      mountTwitchExt(false);
      authService.init();
      store.refresh();
      controller.expectNone(STATE_URL);
    });
  });
});
