/**
 * CSRF Manager
 * Main interface for CSRF protection functionality
 */

import { CSRFSessionManager } from './session-manager';
import { CSRFTokenGenerator } from './token-generator';
import type {
    CSRFConfig,
    CSRFRequest,
    CSRFResponse,
    CSRFToken,
    CSRFValidationResult
} from './types';

export class CSRFManager {
  private tokenGenerator: CSRFTokenGenerator;
  private sessionManager: CSRFSessionManager;
  private config: Required<CSRFConfig>;

  constructor(config: CSRFConfig) {
    this.tokenGenerator = new CSRFTokenGenerator(config);
    this.config = this.tokenGenerator.getConfig();
    this.sessionManager = new CSRFSessionManager(this.config);
  }

  /**
   * Generate a new CSRF token for a session
   */
  generateToken(sessionId: string): CSRFToken {
    const token = this.tokenGenerator.generateToken(sessionId);
    this.sessionManager.addToken(sessionId, token);
    return token;
  }

  /**
   * Validate a CSRF token using session-based validation
   */
  validateToken(tokenValue: string, sessionId: string): CSRFValidationResult {
    // First validate the token structure and signature
    const tokenValidation = this.tokenGenerator.validateToken(tokenValue, sessionId);

    if (!tokenValidation.valid) {
      return tokenValidation;
    }

    // Then check if the token exists in the session
    const sessionValidation = this.sessionManager.validateSessionToken(sessionId, tokenValue);

    return sessionValidation;
  }

  /**
   * Generate and set up double-submit cookie protection
   */
  setupDoubleSubmitProtection(sessionId: string): CSRFResponse {
    const cookieToken = this.tokenGenerator.generateCookieToken();

    return {
      headers: {
        [this.config.headerName]: cookieToken
      },
      cookies: [{
        name: this.config.cookieName,
        value: cookieToken,
        options: {
          httpOnly: this.config.httpOnlyCookie,
          secure: this.config.secureCookie,
          sameSite: this.config.sameSite,
          maxAge: Math.floor(this.config.tokenExpiry / 1000),
          path: '/'
        }
      }]
    };
  }

  /**
   * Validate double-submit cookie pattern
   */
  validateDoubleSubmit(request: CSRFRequest): CSRFValidationResult {
    const cookieToken = request.cookies?.[this.config.cookieName];
    const headerToken = request.headers[this.config.headerName] ||
                       request.headers[this.config.headerName.toLowerCase()];

    const validation = this.tokenGenerator.validateDoubleSubmit(cookieToken || '', headerToken || '');

    return validation;
  }

  /**
   * Validate CSRF protection for a request
   */
  validateRequest(request: CSRFRequest): CSRFValidationResult {
    // If session ID is provided, use session-based validation
    if (request.sessionId) {
      const tokenValue = this.extractTokenFromRequest(request);

      if (!tokenValue) {
        return { valid: false, error: 'CSRF token not found in request' };
      }

      return this.validateToken(tokenValue, request.sessionId);
    }

    // Otherwise, use double-submit cookie pattern
    return this.validateDoubleSubmit(request);
  }

  /**
   * Extract CSRF token from request (header or form field)
   */
  private extractTokenFromRequest(request: CSRFRequest): string | null {
    // Check header first
    const headerToken = request.headers[this.config.headerName] ||
                       request.headers[this.config.headerName.toLowerCase()];

    if (headerToken) {
      return headerToken;
    }

    // Check form field
    if (request.body && typeof request.body === 'object') {
      return request.body[this.config.fieldName] || null;
    }

    return null;
  }

  /**
   * Generate HTML form field for CSRF token
   */
  generateFormField(sessionId: string): string {
    const token = this.generateToken(sessionId);
    return `<input type="hidden" name="${this.config.fieldName}" value="${token.value}" />`;
  }

  /**
   * Generate JavaScript code for automatic token injection
   */
  generateClientScript(sessionId?: string): string {
    const config = {
      headerName: this.config.headerName,
      fieldName: this.config.fieldName,
      cookieName: this.config.cookieName
    };

    return `
(function() {
  const csrfConfig = ${JSON.stringify(config)};

  // Function to get cookie value
  function getCookie(name) {
    const value = "; " + document.cookie;
    const parts = value.split("; " + name + "=");
    if (parts.length === 2) return parts.pop().split(";").shift();
    return null;
  }

  // Function to get CSRF token
  function getCSRFToken() {
    // Try to get from cookie first (double-submit pattern)
    const cookieToken = getCookie(csrfConfig.cookieName);
    if (cookieToken) return cookieToken;

    // Try to get from meta tag
    const metaTag = document.querySelector('meta[name="csrf-token"]');
    if (metaTag) return metaTag.getAttribute('content');

    return null;
  }

  // Inject CSRF token into forms
  function injectFormTokens() {
    const token = getCSRFToken();
    if (!token) return;

    const forms = document.querySelectorAll('form');
    forms.forEach(form => {
      // Skip if token already exists
      if (form.querySelector('input[name="' + csrfConfig.fieldName + '"]')) return;

      const input = document.createElement('input');
      input.type = 'hidden';
      input.name = csrfConfig.fieldName;
      input.value = token;
      form.appendChild(input);
    });
  }

  // Inject CSRF token into AJAX requests
  function setupAjaxInterceptor() {
    const token = getCSRFToken();
    if (!token) return;

    // XMLHttpRequest interceptor
    const originalOpen = XMLHttpRequest.prototype.open;
    const originalSend = XMLHttpRequest.prototype.send;

    XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
      this._method = method;
      return originalOpen.call(this, method, url, async, user, password);
    };

    XMLHttpRequest.prototype.send = function(data) {
      if (this._method && this._method.toUpperCase() !== 'GET') {
        this.setRequestHeader(csrfConfig.headerName, token);
      }
      return originalSend.call(this, data);
    };

    // Fetch interceptor
    if (window.fetch) {
      const originalFetch = window.fetch;
      window.fetch = function(input, init) {
        init = init || {};

        const method = init.method || 'GET';
        if (method.toUpperCase() !== 'GET') {
          init.headers = init.headers || {};
          if (typeof init.headers.set === 'function') {
            init.headers.set(csrfConfig.headerName, token);
          } else {
            init.headers[csrfConfig.headerName] = token;
          }
        }

        return originalFetch.call(this, input, init);
      };
    }
  }

  // Initialize when DOM is ready
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', function() {
      injectFormTokens();
      setupAjaxInterceptor();
    });
  } else {
    injectFormTokens();
    setupAjaxInterceptor();
  }

  // Re-inject tokens when new forms are added
  const observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      if (mutation.type === 'childList') {
        mutation.addedNodes.forEach(function(node) {
          if (node.nodeType === 1) { // Element node
            if (node.tagName === 'FORM') {
              injectFormTokens();
            } else if (node.querySelectorAll) {
              const forms = node.querySelectorAll('form');
              if (forms.length > 0) {
                injectFormTokens();
              }
            }
          }
        });
      }
    });
  });

  observer.observe(document.body, {
    childList: true,
    subtree: true
  });
})();
    `.trim();
  }

  /**
   * Consume a token (for one-time use)
   */
  consumeToken(sessionId: string, tokenValue: string): boolean {
    return this.sessionManager.consumeToken(sessionId, tokenValue);
  }

  /**
   * Remove a session and all its tokens
   */
  removeSession(sessionId: string): boolean {
    return this.sessionManager.removeSession(sessionId);
  }

  /**
   * Get session statistics
   */
  getStats() {
    return this.sessionManager.getStats();
  }

  /**
   * Clean up resources
   */
  destroy(): void {
    this.sessionManager.destroy();
  }

  /**
   * Get configuration
   */
  getConfig(): Required<CSRFConfig> {
    return { ...this.config };
  }
}
