import { HttpClient, provideHttpClient, withInterceptors } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { AUTH_BFF_BASE_URL, AUTH_CSRF_COOKIE_NAME } from './auth.config';
import { csrfInterceptor } from './csrf.interceptor';

const BFF_BASE = 'http://bff.test/api';
const CSRF_COOKIE = 'portal_csrf';

function setup() {
  TestBed.configureTestingModule({
    providers: [
      provideHttpClient(withInterceptors([csrfInterceptor])),
      provideHttpClientTesting(),
      { provide: AUTH_BFF_BASE_URL, useValue: BFF_BASE },
      { provide: AUTH_CSRF_COOKIE_NAME, useValue: CSRF_COOKIE },
    ],
  });
  return {
    http: TestBed.inject(HttpClient),
    httpCtrl: TestBed.inject(HttpTestingController),
  };
}

/**
 * jsdom keeps `document.cookie` as a single mutable string. Wipe it
 * around each test so prior writes don't leak.
 */
function setCookie(name: string, value: string): void {
  document.cookie = `${name}=${value}; path=/`;
}
function clearAllCookies(): void {
  const all = document.cookie.split(';');
  for (const c of all) {
    const eq = c.indexOf('=');
    const name = (eq > -1 ? c.slice(0, eq) : c).trim();
    if (name) document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
  }
}

describe('csrfInterceptor', () => {
  beforeEach(() => clearAllCookies());
  afterEach(() => {
    clearAllCookies();
    TestBed.resetTestingModule();
  });

  it('echoes the cookie value into the X-CSRF-Token header on POST', () => {
    setCookie(CSRF_COOKIE, 'tok-abc');
    const { http, httpCtrl } = setup();
    http.post(`${BFF_BASE}/profile`, { name: 'Jane' }).subscribe();
    const req = httpCtrl.expectOne(`${BFF_BASE}/profile`);
    expect(req.request.headers.get('X-CSRF-Token')).toBe('tok-abc');
    req.flush({});
  });

  it.each(['PUT', 'PATCH', 'DELETE'])('sets the header on %s too', (method) => {
    setCookie(CSRF_COOKIE, 'tok-abc');
    const { http, httpCtrl } = setup();
    http.request(method, `${BFF_BASE}/profile`, { body: {} }).subscribe();
    const req = httpCtrl.expectOne(`${BFF_BASE}/profile`);
    expect(req.request.headers.get('X-CSRF-Token')).toBe('tok-abc');
    req.flush({});
  });

  it('does NOT set the header on GET (BFF skips CSRF on safe methods)', () => {
    setCookie(CSRF_COOKIE, 'tok-abc');
    const { http, httpCtrl } = setup();
    http.get(`${BFF_BASE}/me`).subscribe();
    const req = httpCtrl.expectOne(`${BFF_BASE}/me`);
    expect(req.request.headers.has('X-CSRF-Token')).toBe(false);
    req.flush({});
  });

  it('does NOT touch requests to other origins', () => {
    setCookie(CSRF_COOKIE, 'tok-abc');
    const { http, httpCtrl } = setup();
    http.post('http://otel.example/v1/traces', {}).subscribe();
    const req = httpCtrl.expectOne('http://otel.example/v1/traces');
    expect(req.request.headers.has('X-CSRF-Token')).toBe(false);
    req.flush({});
  });

  it('omits the header when the cookie is missing (lets the BFF respond with its 403)', () => {
    // No setCookie call — cookie isn't set.
    const { http, httpCtrl } = setup();
    http.post(`${BFF_BASE}/profile`, {}).subscribe();
    const req = httpCtrl.expectOne(`${BFF_BASE}/profile`);
    expect(req.request.headers.has('X-CSRF-Token')).toBe(false);
    req.flush({});
  });

  it('URL-decodes the cookie value (cookies are URI-encoded in transit)', () => {
    // Cookies preserve their stored form; if anything URL-encodes
    // the token (current BFF doesn't, base64url is safe), we still
    // round-trip cleanly.
    setCookie(CSRF_COOKIE, encodeURIComponent('A B+/='));
    const { http, httpCtrl } = setup();
    http.post(`${BFF_BASE}/profile`, {}).subscribe();
    const req = httpCtrl.expectOne(`${BFF_BASE}/profile`);
    expect(req.request.headers.get('X-CSRF-Token')).toBe('A B+/=');
    req.flush({});
  });

  it('finds the right cookie when others are also set', () => {
    setCookie('other_cookie', 'noise');
    setCookie(CSRF_COOKIE, 'tok-abc');
    setCookie('yet_another', 'noise');
    const { http, httpCtrl } = setup();
    http.post(`${BFF_BASE}/profile`, {}).subscribe();
    const req = httpCtrl.expectOne(`${BFF_BASE}/profile`);
    expect(req.request.headers.get('X-CSRF-Token')).toBe('tok-abc');
    req.flush({});
  });
});
