import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { ActivatedRoute, provideRouter } from '@angular/router';
import { of, throwError } from 'rxjs';
import { AUTH_BFF_BASE_URL } from 'feature-auth';
import {
  UserScopesService,
  type GrantUserScopeInput,
  type UserScope,
  type UserScopesPayload,
} from './user-scopes.service';
import { UserScopesPage } from './user-scopes';

const SCOPE: UserScope = {
  id: 'scope-1',
  kind: 'etablissement',
  value: '0330800013',
  source: 'seed',
  createdAt: '2026-05-26T10:00:00.000Z',
  expiresAt: null,
};

const PAGE: UserScopesPayload = {
  user: {
    oid: 'target-oid',
    userId: 'user-uuid-1',
    displayName: 'Jane Doe',
    email: 'jane@apf.example',
  },
  scopes: [SCOPE],
};

function setup(opts?: {
  list?: ReturnType<typeof vi.fn>;
  grant?: ReturnType<typeof vi.fn>;
  revoke?: ReturnType<typeof vi.fn>;
}) {
  const list = opts?.list ?? vi.fn().mockReturnValue(of(PAGE));
  const grant = opts?.grant ?? vi.fn().mockReturnValue(of(SCOPE));
  const revoke = opts?.revoke ?? vi.fn().mockReturnValue(of(undefined));
  TestBed.configureTestingModule({
    imports: [UserScopesPage],
    providers: [
      provideHttpClient(),
      provideHttpClientTesting(),
      provideRouter([]),
      { provide: AUTH_BFF_BASE_URL, useValue: 'http://bff.test/api' },
      {
        provide: UserScopesService,
        useValue: { list, grant, revoke },
      },
      {
        provide: ActivatedRoute,
        useValue: { snapshot: { paramMap: new Map([['oid', 'target-oid']]) } },
      },
    ],
  });
  const fixture = TestBed.createComponent(UserScopesPage);
  return { fixture, list, grant, revoke };
}

describe('UserScopesPage', () => {
  it('loads the scopes for the route param oid on init', async () => {
    const { fixture, list } = setup();
    fixture.detectChanges();
    await fixture.whenStable();
    expect(list).toHaveBeenCalledWith('target-oid');
  });

  it('surfaces a 404 with the seed/sign-in hint', async () => {
    const list = vi.fn().mockReturnValue(throwError(() => ({ status: 404 })));
    const { fixture } = setup({ list });
    fixture.detectChanges();
    await fixture.whenStable();
    fixture.detectChanges();
    const html = (fixture.nativeElement as HTMLElement).innerHTML;
    expect(html).toMatch(/User not found/);
  });

  it('forbids a 403 with a friendly error', async () => {
    const list = vi.fn().mockReturnValue(throwError(() => ({ status: 403 })));
    const { fixture } = setup({ list });
    fixture.detectChanges();
    await fixture.whenStable();
    fixture.detectChanges();
    const html = (fixture.nativeElement as HTMLElement).innerHTML;
    expect(html).toMatch(/admin access/);
  });
});

describe('UserScopesPage.grant', () => {
  it('sends value for value-bearing kinds + refreshes the list on success', async () => {
    let grantPayload: GrantUserScopeInput | null = null;
    const grant = vi.fn((_oid: string, payload: GrantUserScopeInput) => {
      grantPayload = payload;
      return of(SCOPE);
    });
    const list = vi.fn().mockReturnValue(of(PAGE));
    const { fixture } = setup({ list, grant });
    fixture.detectChanges();
    await fixture.whenStable();
    const component = fixture.componentInstance as unknown as {
      newKind: { set: (v: string) => void };
      newValue: { set: (v: string) => void };
      grant: () => Promise<void>;
    };
    component.newKind.set('etablissement');
    component.newValue.set('0330800013');
    await component.grant();
    expect(grant).toHaveBeenCalledTimes(1);
    expect(grantPayload).toEqual({ kind: 'etablissement', value: '0330800013' });
    // List re-fetched after the grant.
    expect(list).toHaveBeenCalledTimes(2);
  });

  it('omits value for valueless kinds', async () => {
    let grantPayload: GrantUserScopeInput | null = null;
    const grant = vi.fn((_oid: string, payload: GrantUserScopeInput) => {
      grantPayload = payload;
      return of(SCOPE);
    });
    const { fixture } = setup({ grant });
    fixture.detectChanges();
    await fixture.whenStable();
    const component = fixture.componentInstance as unknown as {
      newKind: { set: (v: string) => void };
      grant: () => Promise<void>;
    };
    component.newKind.set('unrestricted');
    await component.grant();
    expect(grantPayload).toEqual({ kind: 'unrestricted' });
  });
});
