import { describe, it, expect, beforeEach } from 'vitest';
import { Repository } from '../../db/repository.js';
import { authenticateA2aRequest } from './token-auth.js';

function fakeProvider(token: string, payload: any) {
  return { AccessToken: { async find(v: string) { return v === token ? payload : undefined; } } } as any;
}
function reqWith(auth?: string) { return { headers: auth ? { authorization: auth } : {} }; }
const NOW = '2026-06-27T00:00:00Z';

describe('authenticateA2aRequest', () => {
  let repo: Repository;
  beforeEach(() => {
    repo = new Repository(':memory:');
    // Seed active user u1 (required by I1 active-status check)
    (repo as any).db.prepare(
      "INSERT INTO users (id, email, name, avatar_url, role, status, created_at, updated_at) " +
      "VALUES ('u1','u1@test','User One',NULL,'user','active','2026-01-01','2026-01-01')",
    ).run();
    repo.createA2aDelegation({
      id: 'd1', userId: 'u1', clientId: 'cli1', grantId: 'g1',
      grantedSpaceIds: ['s1'], grantedSkills: ['research'], audience: 'https://m/a2a',
      expiresAt: null, revokedAt: null,
    });
  });

  it('resolves a principal for a valid token whose grant maps to a live delegation', async () => {
    const p = fakeProvider('tok', { accountId: 'u1', grantId: 'g1', scope: 'openid a2a.read' });
    const principal = await authenticateA2aRequest(p, repo, reqWith('Bearer tok'), NOW);
    expect(principal?.actingUserId).toBe('u1');
    expect(principal?.delegation.grantedSkills).toEqual(['research']);
  });

  it('returns null when no bearer header', async () => {
    const p = fakeProvider('tok', { accountId: 'u1', grantId: 'g1' });
    expect(await authenticateA2aRequest(p, repo, reqWith(), NOW)).toBeNull();
  });

  it('returns null when token invalid/expired (find returns undefined)', async () => {
    const p = fakeProvider('tok', { accountId: 'u1', grantId: 'g1' });
    expect(await authenticateA2aRequest(p, repo, reqWith('Bearer WRONG'), NOW)).toBeNull();
  });

  it('returns null when grant has no delegation row', async () => {
    const p = fakeProvider('tok', { accountId: 'u1', grantId: 'gX' });
    expect(await authenticateA2aRequest(p, repo, reqWith('Bearer tok'), NOW)).toBeNull();
  });

  it('returns null when delegation is revoked', async () => {
    repo.revokeA2aDelegation('d1', '2026-06-26T00:00:00Z');
    const p = fakeProvider('tok', { accountId: 'u1', grantId: 'g1' });
    expect(await authenticateA2aRequest(p, repo, reqWith('Bearer tok'), NOW)).toBeNull();
  });

  it('returns null when token accountId does not match delegation userId', async () => {
    const p = fakeProvider('tok', { accountId: 'u2', grantId: 'g1' }); // delegation g1 belongs to u1
    expect(await authenticateA2aRequest(p, repo, reqWith('Bearer tok'), NOW)).toBeNull();
  });

  it('returns null when AccessToken.find throws', async () => {
    const p = { AccessToken: { find: async () => { throw new Error('adapter error'); } } } as any;
    expect(await authenticateA2aRequest(p, repo, reqWith('Bearer tok'), NOW)).toBeNull();
  });

  it('returns null when delegation is expired (non-revoked)', async () => {
    repo.createA2aDelegation({
      id: 'dx', userId: 'u1', clientId: 'c', grantId: 'gExp',
      grantedSpaceIds: [], grantedSkills: [], audience: null,
      expiresAt: '2026-01-01T00:00:00Z', revokedAt: null,
    });
    const p = fakeProvider('tokx', { accountId: 'u1', grantId: 'gExp' });
    expect(await authenticateA2aRequest(p, repo, reqWith('Bearer tokx'), NOW)).toBeNull();
  });

  // I1: inactive / missing account
  it('returns null when account has no user row (I1)', async () => {
    // 'ghost' has no user row → getUserById returns null → reject
    const p = fakeProvider('tok', { accountId: 'ghost', grantId: 'g1', scope: 'openid a2a.read' });
    expect(await authenticateA2aRequest(p, repo, reqWith('Bearer tok'), NOW)).toBeNull();
  });

  it('returns null when account is disabled (I1)', async () => {
    (repo as any).db.prepare(
      "INSERT INTO users (id, email, name, avatar_url, role, status, created_at, updated_at) " +
      "VALUES ('u-dis','dis@test','Disabled',NULL,'user','disabled','2026-01-01','2026-01-01')",
    ).run();
    repo.createA2aDelegation({
      id: 'd-dis', userId: 'u-dis', clientId: 'cli1', grantId: 'g-dis',
      grantedSpaceIds: [], grantedSkills: [], audience: 'https://m/a2a',
      expiresAt: null, revokedAt: null,
    });
    const p = fakeProvider('tok-dis', { accountId: 'u-dis', grantId: 'g-dis', scope: 'openid a2a.read' });
    expect(await authenticateA2aRequest(p, repo, reqWith('Bearer tok-dis'), NOW)).toBeNull();
  });

  // M1: token scope lacks a2a.read
  it('returns null when token scope lacks a2a.read (M1)', async () => {
    const p = fakeProvider('tok', { accountId: 'u1', grantId: 'g1', scope: 'openid' });
    expect(await authenticateA2aRequest(p, repo, reqWith('Bearer tok'), NOW)).toBeNull();
  });
});
