/**
 * CSRF Manager Tests
 * Comprehensive tests for CSRF protection functionality
 */

import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { CSRFManager } from './csrf-manager';
import { CSRFConfig, CSRFRequest } from './types';

describe('CSRFManager', () => {
  let csrfManager: CSRFManager;
  let config: CSRFConfig;

  beforeEach(() => {
    config = {
      secret: 'test-secret-key-for-csrf-protection',
      tokenExpiry: 60 * 60 * 1000, // 1 hour
      cookieName: '__csrf-token',
      headerName: 'X-CSRF-Token',
      fieldName: '_csrf',
      secureCookie: true,
      httpOnlyCookie: true,
      sameSite: 'strict'
    };

    csrfManager = new CSRFManager(config);
  });

  afterEach(() => {
    csrfManager.destroy();
  });

  describe('Token Generation', () => {
    it('should generate a valid CSRF token', () => {
      const sessionId = 'test-session-123';
      const token = csrfManager.generateToken(sessionId);

      expect(token).toBeDefined();
      expect(token.value).toBeTruthy();
      expect(token.sessionId).toBe(sessionId);
      expect(token.expiresAt).toBeGreaterThan(Date.now());
    });

    it('should generate unique tokens for the same session', () => {
      const sessionId = 'test-session-123';
      const token1 = csrfManager.generateToken(sessionId);
      const token2 = csrfManager.generateToken(sessionId);

      expect(token1.value).not.toBe(token2.value);
      expect(token1.sessionId).toBe(token2.sessionId);
    });

    it('should generate different tokens for different sessions', () => {
      const token1 = csrfManager.generateToken('session-1');
      const token2 = csrfManager.generateToken('session-2');

      expect(token1.value).not.toBe(token2.value);
      expect(token1.sessionId).not.toBe(token2.sessionId);
    });
  });

  describe('Token Validation', () => {
    it('should validate a correct token', () => {
      const sessionId = 'test-session-123';
      const token = csrfManager.generateToken(sessionId);

      const result = csrfManager.validateToken(token.value, sessionId);

      expect(result.valid).toBe(true);
      expect(result.error).toBeUndefined();
    });

    it('should reject token with wrong session ID', () => {
      const sessionId = 'test-session-123';
      const token = csrfManager.generateToken(sessionId);

      const result = csrfManager.validateToken(token.value, 'wrong-session');

      expect(result.valid).toBe(false);
      expect(result.error).toBeTruthy();
    });

    it('should reject malformed tokens', () => {
      const result = csrfManager.validateToken('invalid-token', 'session-123');

      expect(result.valid).toBe(false);
      expect(result.error).toBeTruthy();
    });

    it('should reject expired tokens', () => {
      // Create manager with very short expiry
      const shortConfig = { ...config, tokenExpiry: 1 }; // 1ms
      const shortManager = new CSRFManager(shortConfig);

      const sessionId = 'test-session-123';
      const token = shortManager.generateToken(sessionId);

      // Wait for token to expire
      return new Promise(resolve => {
        setTimeout(() => {
          const result = shortManager.validateToken(token.value, sessionId);

          expect(result.valid).toBe(false);
          expect(result.expired).toBe(true);

          shortManager.destroy();
          resolve(undefined);
        }, 10);
      });
    });
  });

  describe('Double-Submit Cookie Pattern', () => {
    it('should set up double-submit protection', () => {
      const sessionId = 'test-session-123';
      const response = csrfManager.setupDoubleSubmitProtection(sessionId);

      expect(response.headers).toBeDefined();
      expect(response.headers['X-CSRF-Token']).toBeTruthy();
      expect(response.cookies).toHaveLength(1);

      const cookie = response.cookies[0];
      expect(cookie.name).toBe('__csrf-token');
      expect(cookie.value).toBeTruthy();
      expect(cookie.options.httpOnly).toBe(true);
      expect(cookie.options.secure).toBe(true);
      expect(cookie.options.sameSite).toBe('strict');
    });

    it('should validate matching double-submit tokens', () => {
      const sessionId = 'test-session-123';
      const response = csrfManager.setupDoubleSubmitProtection(sessionId);

      const cookieToken = response.cookies[0].value;
      const headerToken = response.headers['X-CSRF-Token'];

      const request: CSRFRequest = {
        headers: { 'X-CSRF-Token': headerToken },
        cookies: { '__csrf-token': cookieToken }
      };

      const result = csrfManager.validateDoubleSubmit(request);

      expect(result.valid).toBe(true);
    });

    it('should reject mismatched double-submit tokens', () => {
      const sessionId = 'test-session-123';
      const response1 = csrfManager.setupDoubleSubmitProtection(sessionId);
      const response2 = csrfManager.setupDoubleSubmitProtection(sessionId);

      const request: CSRFRequest = {
        headers: { 'X-CSRF-Token': response1.headers['X-CSRF-Token'] },
        cookies: { '__csrf-token': response2.cookies[0].value }
      };

      const result = csrfManager.validateDoubleSubmit(request);

      expect(result.valid).toBe(false);
      expect(result.error).toBeTruthy();
    });

    it('should reject missing double-submit tokens', () => {
      const request: CSRFRequest = {
        headers: {},
        cookies: {}
      };

      const result = csrfManager.validateDoubleSubmit(request);

      expect(result.valid).toBe(false);
      expect(result.error).toBe('Missing CSRF tokens');
    });
  });

  describe('Request Validation', () => {
    it('should validate request with session-based token in header', () => {
      const sessionId = 'test-session-123';
      const token = csrfManager.generateToken(sessionId);

      const request: CSRFRequest = {
        headers: { 'X-CSRF-Token': token.value },
        sessionId
      };

      const result = csrfManager.validateRequest(request);

      expect(result.valid).toBe(true);
    });

    it('should validate request with session-based token in form field', () => {
      const sessionId = 'test-session-123';
      const token = csrfManager.generateToken(sessionId);

      const request: CSRFRequest = {
        headers: {},
        body: { _csrf: token.value },
        sessionId
      };

      const result = csrfManager.validateRequest(request);

      expect(result.valid).toBe(true);
    });

    it('should validate request with double-submit pattern when no session ID', () => {
      const sessionId = 'test-session-123';
      const response = csrfManager.setupDoubleSubmitProtection(sessionId);

      const request: CSRFRequest = {
        headers: { 'X-CSRF-Token': response.headers['X-CSRF-Token'] },
        cookies: { '__csrf-token': response.cookies[0].value }
      };

      const result = csrfManager.validateRequest(request);

      expect(result.valid).toBe(true);
    });

    it('should reject request without CSRF token', () => {
      const request: CSRFRequest = {
        headers: {},
        sessionId: 'test-session-123'
      };

      const result = csrfManager.validateRequest(request);

      expect(result.valid).toBe(false);
      expect(result.error).toBe('CSRF token not found in request');
    });
  });

  describe('Form Field Generation', () => {
    it('should generate HTML form field', () => {
      const sessionId = 'test-session-123';
      const formField = csrfManager.generateFormField(sessionId);

      expect(formField).toContain('type="hidden"');
      expect(formField).toContain('name="_csrf"');
      expect(formField).toContain('value=');
      expect(formField).toMatch(/<input[^>]*>/);
    });

    it('should generate form field with valid token', () => {
      const sessionId = 'test-session-123';
      const formField = csrfManager.generateFormField(sessionId);

      // Extract token value from HTML
      const match = formField.match(/value="([^"]+)"/);
      expect(match).toBeTruthy();

      if (match) {
        const tokenValue = match[1];
        const result = csrfManager.validateToken(tokenValue, sessionId);
        expect(result.valid).toBe(true);
      }
    });
  });

  describe('Client Script Generation', () => {
    it('should generate client-side JavaScript', () => {
      const script = csrfManager.generateClientScript('test-session');

      expect(script).toContain('csrfConfig');
      expect(script).toContain('X-CSRF-Token');
      expect(script).toContain('__csrf-token');
      expect(script).toContain('_csrf');
      expect(script).toContain('XMLHttpRequest');
      expect(script).toContain('fetch');
    });

    it('should include configuration in generated script', () => {
      const script = csrfManager.generateClientScript();

      expect(script).toContain('"headerName":"X-CSRF-Token"');
      expect(script).toContain('"fieldName":"_csrf"');
      expect(script).toContain('"cookieName":"__csrf-token"');
    });
  });

  describe('Token Consumption', () => {
    it('should consume token successfully', () => {
      const sessionId = 'test-session-123';
      const token = csrfManager.generateToken(sessionId);

      const consumed = csrfManager.consumeToken(sessionId, token.value);
      expect(consumed).toBe(true);

      // Token should no longer be valid after consumption
      const result = csrfManager.validateToken(token.value, sessionId);
      expect(result.valid).toBe(false);
    });

    it('should return false for non-existent token', () => {
      const consumed = csrfManager.consumeToken('session-123', 'non-existent-token');
      expect(consumed).toBe(false);
    });
  });

  describe('Session Management', () => {
    it('should remove session and all tokens', () => {
      const sessionId = 'test-session-123';
      const token1 = csrfManager.generateToken(sessionId);
      const token2 = csrfManager.generateToken(sessionId);

      const removed = csrfManager.removeSession(sessionId);
      expect(removed).toBe(true);

      // Tokens should no longer be valid
      const result1 = csrfManager.validateToken(token1.value, sessionId);
      const result2 = csrfManager.validateToken(token2.value, sessionId);

      expect(result1.valid).toBe(false);
      expect(result2.valid).toBe(false);
    });

    it('should return false when removing non-existent session', () => {
      const removed = csrfManager.removeSession('non-existent-session');
      expect(removed).toBe(false);
    });
  });

  describe('Statistics', () => {
    it('should provide session statistics', () => {
      const sessionId1 = 'session-1';
      const sessionId2 = 'session-2';

      csrfManager.generateToken(sessionId1);
      csrfManager.generateToken(sessionId1);
      csrfManager.generateToken(sessionId2);

      const stats = csrfManager.getStats();

      expect(stats.totalSessions).toBe(2);
      expect(stats.totalTokens).toBe(3);
      expect(stats.activeSessions).toBe(2);
    });
  });

  describe('Configuration', () => {
    it('should return configuration', () => {
      const returnedConfig = csrfManager.getConfig();

      expect(returnedConfig.secret).toBe(config.secret);
      expect(returnedConfig.tokenExpiry).toBe(config.tokenExpiry);
      expect(returnedConfig.cookieName).toBe(config.cookieName);
      expect(returnedConfig.headerName).toBe(config.headerName);
      expect(returnedConfig.fieldName).toBe(config.fieldName);
    });

    it('should use default values for optional config', () => {
      const minimalConfig: CSRFConfig = {
        secret: 'test-secret'
      };

      const manager = new CSRFManager(minimalConfig);
      const returnedConfig = manager.getConfig();

      expect(returnedConfig.tokenExpiry).toBe(60 * 60 * 1000); // 1 hour default
      expect(returnedConfig.cookieName).toBe('__csrf-token');
      expect(returnedConfig.headerName).toBe('X-CSRF-Token');
      expect(returnedConfig.fieldName).toBe('_csrf');
      expect(returnedConfig.secureCookie).toBe(true);
      expect(returnedConfig.httpOnlyCookie).toBe(true);
      expect(returnedConfig.sameSite).toBe('strict');

      manager.destroy();
    });
  });
});
