import { TestBed } from '@angular/core/testing';
import { provideRouter } from '@angular/router';
import { of, throwError } from 'rxjs';
import {
  AdminUsersService,
  type AdminUsersPage,
  type AdminUsersQuery,
} from './admin-users.service';
import { UsersPage } from './users';

const PAGE: AdminUsersPage = {
  items: [
    {
      oid: 'jane-oid',
      tid: 'tenant-1',
      audience: 'workforce',
      username: 'jane.doe@apf.example',
      displayName: 'Jane Doe',
      firstSeenAt: '2026-05-10T10:00:00.000Z',
      lastSeenAt: '2026-05-14T08:30:00.000Z',
    },
    {
      oid: 'admin-oid',
      tid: 'tenant-1',
      audience: 'workforce',
      username: 'admin@apf.example',
      displayName: 'Admin Smith',
      firstSeenAt: '2026-05-09T08:00:00.000Z',
      lastSeenAt: '2026-05-13T17:45:00.000Z',
    },
  ],
  total: 137,
  limit: 50,
  offset: 0,
};

interface Fixture {
  fixture: ReturnType<typeof TestBed.createComponent<UsersPage>>;
  query: ReturnType<typeof vi.fn>;
}

function setup(opts?: { initial?: AdminUsersPage | 'error'; status?: number }): Fixture {
  const initial = opts?.initial ?? PAGE;
  const query = vi.fn().mockImplementation(() => {
    if (initial === 'error') {
      return throwError(() => ({ status: opts?.status ?? 500 }));
    }
    return of(initial);
  });
  TestBed.configureTestingModule({
    imports: [UsersPage],
    providers: [{ provide: AdminUsersService, useValue: { query } }, provideRouter([])],
  });
  const fixture = TestBed.createComponent(UsersPage);
  return { fixture, query };
}

async function flush(fixture: Fixture['fixture']): Promise<void> {
  fixture.detectChanges();
  await Promise.resolve();
  await Promise.resolve();
  fixture.detectChanges();
  await fixture.whenStable();
}

describe('UsersPage', () => {
  it('runs an initial unfiltered query on init and renders the result table', async () => {
    const { fixture, query } = setup();
    await flush(fixture);
    expect(query).toHaveBeenCalledTimes(1);
    expect(query).toHaveBeenCalledWith(expect.objectContaining({ limit: 50, offset: 0 }));
    const rows = (fixture.nativeElement as HTMLElement).querySelectorAll<HTMLTableRowElement>(
      '.users-table tbody tr',
    );
    expect(rows.length).toBe(2);
  });

  it('shows the result range ("1–2 of 137") in the status bar', async () => {
    const { fixture } = setup();
    await flush(fixture);
    expect(
      (fixture.nativeElement as HTMLElement).querySelector('.status-line')?.textContent,
    ).toContain('1–2 of 137');
  });

  it('renders the empty state when the page has no items', async () => {
    const { fixture } = setup({
      initial: { items: [], total: 0, limit: 50, offset: 0 },
    });
    await flush(fixture);
    const root = fixture.nativeElement as HTMLElement;
    expect(root.querySelector('.empty')?.textContent).toContain('No users');
    expect(root.querySelector('.users-table')).toBeNull();
  });

  it('renders a permission-aware error message on 403', async () => {
    const { fixture } = setup({ initial: 'error', status: 403 });
    await flush(fixture);
    const status = (fixture.nativeElement as HTMLElement).querySelector('.status-line--error');
    expect(status?.textContent).toContain('do not have access');
  });

  it('renders a generic error message on 5xx', async () => {
    const { fixture } = setup({ initial: 'error', status: 500 });
    await flush(fixture);
    const status = (fixture.nativeElement as HTMLElement).querySelector('.status-line--error');
    expect(status?.textContent).toContain('Could not load');
  });

  it('passes the populated filter fields to the service on Apply', async () => {
    const { fixture, query } = setup();
    await flush(fixture);
    const component = fixture.componentInstance;
    (component as unknown as { username: { set: (v: string) => void } }).username.set('jane');
    (component as unknown as { audience: { set: (v: string) => void } }).audience.set('workforce');
    await component.search();
    await flush(fixture);
    expect(query).toHaveBeenCalledTimes(2);
    const lastCall = query.mock.calls[1]?.[0] as AdminUsersQuery;
    expect(lastCall.username).toBe('jane');
    expect(lastCall.audience).toBe('workforce');
    expect(lastCall.offset).toBe(0);
  });

  it('resets the offset to 0 on each Apply', async () => {
    const { fixture, query } = setup();
    await flush(fixture);
    const component = fixture.componentInstance;
    await component.next();
    await flush(fixture);
    expect((query.mock.calls[1]?.[0] as AdminUsersQuery).offset).toBe(50);
    await component.search();
    await flush(fixture);
    expect((query.mock.calls[2]?.[0] as AdminUsersQuery).offset).toBe(0);
  });

  it('clearFilters empties every filter signal and re-queries from offset 0', async () => {
    const { fixture, query } = setup();
    await flush(fixture);
    const component = fixture.componentInstance;
    (component as unknown as { username: { set: (v: string) => void } }).username.set('x');
    (component as unknown as { displayName: { set: (v: string) => void } }).displayName.set('y');
    await component.clearFilters();
    await flush(fixture);
    const lastCall = query.mock.calls[1]?.[0] as AdminUsersQuery;
    expect(lastCall.username).toBeUndefined();
    expect(lastCall.displayName).toBeUndefined();
    expect(lastCall.offset).toBe(0);
  });

  it('Next is disabled when the loaded page covers the total', async () => {
    const { fixture } = setup({
      initial: { items: PAGE.items, total: 2, limit: 50, offset: 0 },
    });
    await flush(fixture);
    const root = fixture.nativeElement as HTMLElement;
    const nextBtn = Array.from(root.querySelectorAll<HTMLButtonElement>('.pagination .btn')).find(
      (b) => b.textContent?.trim() === 'Next',
    );
    expect(nextBtn?.disabled).toBe(true);
  });

  it('Previous is disabled at offset 0', async () => {
    const { fixture } = setup();
    await flush(fixture);
    const root = fixture.nativeElement as HTMLElement;
    const prevBtn = Array.from(root.querySelectorAll<HTMLButtonElement>('.pagination .btn')).find(
      (b) => b.textContent?.trim() === 'Previous',
    );
    expect(prevBtn?.disabled).toBe(true);
  });

  it('renders the displayName + username + audience badge per row', async () => {
    const { fixture } = setup();
    await flush(fixture);
    const root = fixture.nativeElement as HTMLElement;
    const rows = root.querySelectorAll<HTMLTableRowElement>('.users-table tbody tr');
    expect(rows[0]?.querySelector('.cell-name')?.textContent?.trim()).toBe('Jane Doe');
    expect(rows[0]?.querySelector('.cell-username')?.textContent?.trim()).toBe(
      'jane.doe@apf.example',
    );
    expect(rows[0]?.querySelector('.aud-badge')?.textContent?.trim()).toBe('workforce');
  });
});
