import type { Request, Response, NextFunction } from "express";
import crypto from "crypto";

interface CSRFStore {
  [token: string]: {
    createdAt: number;
    expiresAt: number;
    used: boolean;
  };
}

// In-memory store for CSRF tokens (in production, use Redis or database)
const csrfTokenStore: CSRFStore = {};

const CSRF_TOKEN_EXPIRY = 30 * 60 * 1000; // 30 minutes
const CSRF_TOKEN_LENGTH = 32;

/**
 * Generate a CSRF token
 */
export function generateCSRFToken(): string {
  const token = crypto.randomBytes(CSRF_TOKEN_LENGTH).toString("hex");
  const now = Date.now();

  csrfTokenStore[token] = {
    createdAt: now,
    expiresAt: now + CSRF_TOKEN_EXPIRY,
    used: false,
  };

  return token;
}

/**
 * Verify a CSRF token
 */
export function verifyCSRFToken(token: string): boolean {
  const tokenData = csrfTokenStore[token];

  if (!tokenData) {
    console.warn("[CSRF] Token not found");
    return false;
  }

  const now = Date.now();

  // Check if token has expired
  if (now > tokenData.expiresAt) {
    console.warn("[CSRF] Token has expired");
    delete csrfTokenStore[token];
    return false;
  }

  // Check if token has already been used
  if (tokenData.used) {
    console.warn("[CSRF] Token has already been used (possible replay attack)");
    delete csrfTokenStore[token];
    return false;
  }

  // Mark token as used
  tokenData.used = true;

  // Clean up after use
  setTimeout(() => {
    delete csrfTokenStore[token];
  }, 5000);

  return true;
}

/**
 * CSRF protection middleware
 */
export function csrfProtection(req: Request, res: Response, next: NextFunction) {
  // Skip CSRF check for GET requests
  if (req.method === "GET") {
    return next();
  }

  const token = req.body.csrfToken || req.headers["x-csrf-token"];

  if (!token) {
    console.error("[CSRF] No CSRF token provided");
    return res.status(403).json({
      error: "CSRF token missing",
    });
  }

  if (!verifyCSRFToken(token as string)) {
    console.error("[CSRF] Invalid or expired CSRF token");
    return res.status(403).json({
      error: "Invalid CSRF token",
    });
  }

  next();
}

/**
 * OAuth CSRF protection middleware
 */
export function oauthCSRFProtection(req: Request, res: Response, next: NextFunction) {
  if (req.method === "GET") {
    // Generate CSRF token for OAuth flow
    const token = generateCSRFToken();
    req.session = req.session || {};
    (req.session as any).csrfToken = token;
    return next();
  }

  // Verify CSRF token on callback
  const token = req.query.state || req.body.state;

  if (!token) {
    console.error("[OAuth CSRF] No state parameter provided");
    return res.status(403).json({
      error: "Missing state parameter",
    });
  }

  if (!verifyCSRFToken(token as string)) {
    console.error("[OAuth CSRF] Invalid or expired state parameter");
    return res.status(403).json({
      error: "Invalid state parameter",
    });
  }

  next();
}

/**
 * Clean up expired CSRF tokens
 */
export function cleanupExpiredCSRFTokens() {
  const now = Date.now();
  let cleaned = 0;

  for (const token in csrfTokenStore) {
    if (now > csrfTokenStore[token].expiresAt) {
      delete csrfTokenStore[token];
      cleaned++;
    }
  }

  console.log(`[CSRF] Cleaned up ${cleaned} expired tokens`);
  return cleaned;
}

// Run cleanup every 10 minutes
setInterval(cleanupExpiredCSRFTokens, 10 * 60 * 1000);

/**
 * Get CSRF token from session
 */
export function getCSRFToken(req: Request): string | null {
  return (req.session as any)?.csrfToken || null;
}

/**
 * Set CSRF token in session
 */
export function setCSRFToken(req: Request, token: string) {
  req.session = req.session || {};
  (req.session as any).csrfToken = token;
}

/**
 * Generate OAuth state parameter with CSRF protection
 */
export function generateOAuthState(userId?: number): string {
  const state = generateCSRFToken();
  return state;
}

/**
 * Verify OAuth state parameter
 */
export function verifyOAuthState(state: string): boolean {
  return verifyCSRFToken(state);
}
