import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { provideRouter } from '@angular/router';
import { AUTH_BFF_BASE_URL, type CurrentUser } from 'feature-auth';
import { Sidebar } from './sidebar';

const BFF_BASE = 'http://bff.test/api';
const ME_URL = `${BFF_BASE}/auth/me`;
const CAPABILITIES_URL = `${BFF_BASE}/me/capabilities`;

interface SetupOpts {
  user?: CurrentUser | null;
  canAccessAdmin?: boolean;
}

async function setup(opts: SetupOpts = {}) {
  TestBed.resetTestingModule();
  TestBed.configureTestingModule({
    imports: [Sidebar],
    providers: [
      provideRouter([]),
      provideHttpClient(),
      provideHttpClientTesting(),
      { provide: AUTH_BFF_BASE_URL, useValue: BFF_BASE },
    ],
  });
  const fixture = TestBed.createComponent(Sidebar);
  const http = TestBed.inject(HttpTestingController);

  // AuthService fires `GET /auth/me` from its constructor; flush
  // synchronously so the auth-state signal settles before any
  // template assertion. `CapabilitiesService` then fires
  // `GET /me/capabilities` via its `effect` once `currentUser`
  // flips — running detectChanges + whenStable gives Angular's
  // scheduler a chance to dispatch the effect before we look up
  // the second request.
  const meReq = http.expectOne(ME_URL);
  if (opts.user) {
    meReq.flush(opts.user);
    // Two microtask rounds: the first lets `AuthService.refresh()`
    // resume past `await firstValueFrom(...)` and call
    // `this._state.set(...)`; the second lets `CapabilitiesService`'s
    // effect dispatch and queue its own GET. detectChanges in
    // between forces signal-driven effect scheduling under
    // zoneless change detection.
    await flushPendingEffects(fixture);
    const capsReq = http.expectOne(CAPABILITIES_URL);
    capsReq.flush({ canAccessAdmin: opts.canAccessAdmin ?? false });
  } else {
    meReq.flush({}, { status: 401, statusText: 'Unauthorized' });
  }
  await flushPendingEffects(fixture);
  return { fixture, http };
}

async function flushPendingEffects(fixture: ReturnType<typeof TestBed.createComponent>) {
  for (let i = 0; i < 4; i++) {
    fixture.detectChanges();
    await fixture.whenStable();
    await Promise.resolve();
  }
}

describe('Sidebar', () => {
  beforeEach(() => localStorage.clear());

  it('renders the three v1 menu groups', async () => {
    const { fixture } = await setup();
    const text = (fixture.nativeElement as HTMLElement).textContent ?? '';
    expect(text).toContain('General');
    expect(text).toContain('Establishments');
    expect(text).toContain('Communication');
  });

  it('does not render the accessibility links (now lives in the footer)', async () => {
    const { fixture } = await setup();
    const root = fixture.nativeElement as HTMLElement;
    expect(root.querySelector('a[href="/accessibility"]')).toBeNull();
    expect(root.querySelector('a[href="/accessibilite"]')).toBeNull();
  });

  it('starts expanded and collapses on toggle, persisting to localStorage', async () => {
    const { fixture } = await setup();
    const root = fixture.nativeElement as HTMLElement;
    const aside = root.querySelector('aside');
    expect(aside?.classList.contains('sidebar--collapsed')).toBe(false);

    const toggle = root.querySelector('button.sidebar__toggle') as HTMLButtonElement;
    toggle.click();
    fixture.detectChanges();
    await fixture.whenStable();

    expect(aside?.classList.contains('sidebar--collapsed')).toBe(true);
    expect(toggle.getAttribute('aria-expanded')).toBe('false');
    expect(localStorage.getItem('portal-shell:sidebar-collapsed')).toBe('1');
  });

  it('restores the collapsed state from localStorage on init', async () => {
    localStorage.setItem('portal-shell:sidebar-collapsed', '1');
    const { fixture } = await setup();
    const aside = (fixture.nativeElement as HTMLElement).querySelector('aside');
    expect(aside?.classList.contains('sidebar--collapsed')).toBe(true);
  });

  describe('role label', () => {
    it('renders "Anonymous" when the session is missing', async () => {
      const { fixture } = await setup();
      const label = (fixture.nativeElement as HTMLElement).querySelector(
        '[data-testid="sidebar-role"]',
      );
      expect(label?.textContent?.trim()).toBe('Anonymous');
    });

    it('renders "User" when the session is authenticated without admin', async () => {
      const { fixture } = await setup({
        user: {
          oid: 'user-oid',
          tid: 'tenant',
          username: 'jane.doe@apf.example',
          displayName: 'Jane Doe',
        },
        canAccessAdmin: false,
      });
      const label = (fixture.nativeElement as HTMLElement).querySelector(
        '[data-testid="sidebar-role"]',
      );
      expect(label?.textContent?.trim()).toBe('User');
    });

    it('renders "Administrator" when capabilities flag canAccessAdmin', async () => {
      const { fixture } = await setup({
        user: {
          oid: 'admin-oid',
          tid: 'tenant',
          username: 'admin@apf.example',
          displayName: 'Admin Smith',
        },
        canAccessAdmin: true,
      });
      const label = (fixture.nativeElement as HTMLElement).querySelector(
        '[data-testid="sidebar-role"]',
      );
      expect(label?.textContent?.trim()).toBe('Administrator');
    });
  });
});
