import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import {
  signAgentToken,
  verifyAgentToken,
  assertToolAllowed,
  getSigningSecret,
  validateMintRequest,
  DEFAULT_DEV_SIGNING_SECRET,
  MAX_TOKEN_TTL_HOURS,
  type AgentTokenData,
} from '../security/agent-token.js';

const TEST_SECRET = 'test-signing-secret-at-least-32-chars!!';
const ORIGINAL_ENV = { ...process.env };

function futurePayload(tools: string[] = ['http_fetch']): AgentTokenData {
  return {
    agent_id: 'agent-test-1',
    tools,
    permissions: ['read'],
    expires_at: new Date(Date.now() + 3600_000).toISOString(),
  };
}

describe('agent-token signing', () => {
  beforeEach(() => {
    process.env = { ...ORIGINAL_ENV, NODE_ENV: 'development', SIGNING_SECRET: TEST_SECRET };
  });

  afterEach(() => {
    process.env = ORIGINAL_ENV;
  });

  it('signs and verifies a valid token', () => {
    const signed = signAgentToken(futurePayload(), TEST_SECRET);
    expect('token' in signed).toBe(true);
    if (!('token' in signed)) return;

    const verified = verifyAgentToken(signed.token);
    expect(verified.valid).toBe(true);
    expect(verified.data?.agent_id).toBe('agent-test-1');
    expect(verified.data?.tools).toEqual(['http_fetch']);
  });

  it('rejects tampered signature', () => {
    const signed = signAgentToken(futurePayload(), TEST_SECRET);
    if (!('token' in signed)) throw new Error('expected token');
    const tampered = `${signed.token.slice(0, -1)}x`;

    const verified = verifyAgentToken(tampered);
    expect(verified.valid).toBe(false);
    expect(verified.error).toBe('Invalid signature');
  });

  it('rejects expired token', () => {
    const expired: AgentTokenData = {
      ...futurePayload(),
      expires_at: new Date(Date.now() - 1000).toISOString(),
    };
    const signed = signAgentToken(expired, TEST_SECRET);
    if (!('token' in signed)) throw new Error('expected token');

    const verified = verifyAgentToken(signed.token);
    expect(verified.valid).toBe(false);
    expect(verified.error).toBe('Token expired');
  });

  it('rejects malformed token format', () => {
    const verified = verifyAgentToken('not-a-valid-token');
    expect(verified.valid).toBe(false);
    expect(verified.error).toBe('Invalid token format');
  });

  it('uses dev default secret when SIGNING_SECRET is short in non-production', () => {
    delete process.env['SIGNING_SECRET'];
    process.env['NODE_ENV'] = 'development';

    const { secret } = getSigningSecret();
    expect(secret).toBe(DEFAULT_DEV_SIGNING_SECRET);
  });

  it('requires SIGNING_SECRET length in production', () => {
    process.env['NODE_ENV'] = 'production';
    delete process.env['SIGNING_SECRET'];

    const { secret, error } = getSigningSecret();
    expect(secret).toBe('');
    expect(error).toMatch(/SIGNING_SECRET/);

    const verified = verifyAgentToken('aaaa.bbbb');
    expect(verified.valid).toBe(false);
    expect(verified.error).toMatch(/SIGNING_SECRET/);
  });

  it('rejects default placeholder SIGNING_SECRET in production at runtime', () => {
    process.env['NODE_ENV'] = 'production';
    process.env['SIGNING_SECRET'] = DEFAULT_DEV_SIGNING_SECRET;

    const { secret, error } = getSigningSecret();
    expect(secret).toBe('');
    expect(error).toMatch(/placeholder/);
  });

  it('rejects invalid expires_at in token payload', () => {
    const bad: AgentTokenData = {
      ...futurePayload(),
      expires_at: 'not-a-date',
    };
    const signed = signAgentToken(bad, TEST_SECRET);
    if (!('token' in signed)) throw new Error('expected token');

    const verified = verifyAgentToken(signed.token);
    expect(verified.valid).toBe(false);
    expect(verified.error).toBe('Invalid token expiry');
  });
});

describe('validateMintRequest', () => {
  it('accepts valid mint body', () => {
    const result = validateMintRequest({
      agent_id: 'a1',
      tools: ['http_fetch'],
      permissions: ['read'],
      expiresInHours: 24,
    });
    expect(result.ok).toBe(true);
    if (!result.ok) return;
    expect(result.data.tools).toEqual(['http_fetch']);
  });

  it('rejects empty tools array', () => {
    const result = validateMintRequest({
      agent_id: 'a1',
      tools: [],
      permissions: ['read'],
    });
    expect(result.ok).toBe(false);
  });

  it('rejects expiresInHours above max', () => {
    const result = validateMintRequest({
      agent_id: 'a1',
      tools: ['http_fetch'],
      permissions: [],
      expiresInHours: MAX_TOKEN_TTL_HOURS + 1,
    });
    expect(result.ok).toBe(false);
  });
});

describe('assertToolAllowed', () => {
  it('allows tool in allowlist', () => {
    const data = futurePayload(['http_fetch', 'serpapi']);
    expect(assertToolAllowed(data, 'http_fetch')).toEqual({ allowed: true });
  });

  it('denies tool not in allowlist', () => {
    const data = futurePayload(['http_fetch']);
    const result = assertToolAllowed(data, 'serpapi');
    expect(result.allowed).toBe(false);
    if (result.allowed) return;
    expect(result.allowedTools).toEqual(['http_fetch']);
  });
});
