import unirest from 'unirest';
import { randomBytes, createHmac } from 'crypto';
import { RateLimiterMemory } from 'rate-limiter-flexible';
import sanitizeHtml from 'sanitize-html';
import helmet from 'helmet';
import csurf from 'csurf';

// Environment variable validation
const requiredEnvVars = [
  'ADMIN_SECRET_KEY',
  'AUTH_ENDPOINT',
  'AUTH_PASSWORD',
  'AUTO_LOGIN_LINK_PREFIX',
  'AUTO_LOGIN_REDIRECT_URL',
  'LINK_ID_BYTES',
  'LINK_EXPIRY_SECONDS',
  'TEMP_LINKS_CLEANUP_INTERVAL_MS'
];

for (const envVar of requiredEnvVars) {
  if (!process.env[envVar]) {
    console.log(`Missing required environment variable: ${envVar}`);
    process.exit(1);
  }
}

// Initialize security middleware
const csrfProtection = csurf({ cookie: { secure: true, httpOnly: true, sameSite: 'strict' } });
const rateLimiter = new RateLimiterMemory({
  points: 10,
  duration: 60 // 10 requests per minute
});

const temporaryLinks = new Map();

// Secure cleanup interval
const cleanupInterval = Math.max(60000, parseInt(process.env.TEMP_LINKS_CLEANUP_INTERVAL_MS, 10));
setInterval(() => {
  const now = Date.now();
  for (const [linkId, linkData] of temporaryLinks.entries()) {
    if (linkData.expiresAt < now) {
      temporaryLinks.delete(linkId);
      console.log(`Cleaned up expired link: ${linkId}`);
    }
  }
}, cleanupInterval);

// Input sanitization and validation
const sanitizeInput = (input) => {
  if (typeof input !== 'string') {
    console.log(`Invalid input type: expected string, got ${typeof input}`);
    return null;
  }
  // Allow alphanumeric characters and underscores
  if (!/^[a-zA-Z0-9_]+$/.test(input)) {
    console.log(`Invalid input format: ${input}`);
    return null;
  }
  return sanitizeHtml(input);
};

// Generate secure token
const generateSecureToken = (bytes) => {
  const buffer = randomBytes(Math.max(16, parseInt(bytes, 10)));
  const hmac = createHmac('sha256', process.env.ADMIN_SECRET_KEY);
  return hmac.update(buffer).digest('hex');
};

// Check if IP is local (for development)
const isLocalIp = (ip) => {
  return (
    ip === '127.0.0.1' ||
    ip === '::1' ||
    ip.startsWith('192.168.') ||
    ip.startsWith('10.')
  );
};

// Get real client IP from Cloudflare or fallback
const getRealIp = (req) => {
  // Prioritize CF-Connecting-IP for Cloudflare
  const cfConnectingIp = req.headers['cf-connecting-ip'];
  if (cfConnectingIp) {
    return cfConnectingIp;
  }

  // Fallback to X-Forwarded-For
  const forwardedFor = req.headers['x-forwarded-for'];
  if (forwardedFor) {
    // Take the first IP in the chain (original client IP)
    return forwardedFor.split(',')[0].trim();
  }

  // Fallback to req.ip
  return req.ip;
};

// Generate nonce for CSP
const generateNonce = () => {
  return randomBytes(16).toString('base64');
};

export async function generateLoginLink(req, res) {
  // Apply security headers
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        styleSrc: ["'self'", "'unsafe-inline'"],
        scriptSrc: ["'self'"]
      }
    },
    xFrameOptions: { action: 'deny' }
  })(req, res, async () => {
    try {
      // Rate limiting with real IP
      await rateLimiter.consume(getRealIp(req));

      // CSRF protection
      csrfProtection(req, res, async () => {
        const { secretKey, username } = req.body;

        // Validate inputs
        if (!sanitizeInput(secretKey) || secretKey !== process.env.ADMIN_SECRET_KEY) {
          console.log(`Invalid secret key attempt from IP: ${getRealIp(req)}`);
          return res.status(401).json({ error: 'Unauthorized' });
        }

        const sanitizedUsername = sanitizeInput(username);
        if (!sanitizedUsername) {
          console.log(`Invalid username attempt from IP: ${getRealIp(req)}, username: ${username}`);
          return res.status(400).json({ error: 'Invalid username' });
        }

        // Secure API request
        const tokenResponse = await unirest
          .post(process.env.AUTH_ENDPOINT)
          .headers({
            'Accept': 'application/json',
            'Content-Type': 'application/json',
            'X-Request-ID': generateSecureToken(16)
          })
          .send({ username: sanitizedUsername, password: process.env.AUTH_PASSWORD })
          .timeout(5000);

        if (!tokenResponse.body.token) {
          console.log(`Failed to generate API key for username: ${sanitizedUsername}`);
          return res.status(500).json({ error: 'Authentication service error' });
        }

        const apiKey = tokenResponse.body.token;
        const linkId = generateSecureToken(process.env.LINK_ID_BYTES);
        const loginLink = `${process.env.AUTO_LOGIN_LINK_PREFIX}${linkId}`;

        // Store link data with real IP
        temporaryLinks.set(linkId, {
          apiKey,
          username: sanitizedUsername,
          expiresAt: Date.now() + Math.min(3600000, parseInt(process.env.LINK_EXPIRY_SECONDS, 10) * 1000),
          ip: getRealIp(req),
          userAgent: req.get('User-Agent') || 'Discord-Bot-Request'
        });

        // Secure timeout
        setTimeout(() => {
          temporaryLinks.delete(linkId);
          console.log(`Expired link removed: ${linkId}`);
        }, Math.min(3600000, parseInt(process.env.LINK_EXPIRY_SECONDS, 10) * 1000));

        console.log(`Generated login link for username: ${sanitizedUsername} from IP: ${getRealIp(req)}, userAgent: ${req.get('User-Agent') || 'Discord-Bot-Request'}`);
        res.json({ loginLink });
      });
    } catch (error) {
      console.log(`Error generating login link: ${error.message}`);
      res.status(500).json({ error: 'Server error' });
    }
  });
}

export function handleAutoLogin(req, res) {
  // Generate nonce for CSP
  const nonce = generateNonce();

  // Apply security headers with nonce
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        styleSrc: ["'self'", "'unsafe-inline'"],
        scriptSrc: ["'self'", `'nonce-${nonce}'`]
      }
    },
    xFrameOptions: { action: 'deny' }
  })(req, res, () => {
    const { linkId } = req.params;
    const sanitizedLinkId = sanitizeInput(linkId);
    const linkData = temporaryLinks.get(sanitizedLinkId);

    if (!linkData || linkData.expiresAt < Date.now()) {
      temporaryLinks.delete(sanitizedLinkId);
      console.log(`Expired or invalid login attempt for link: ${sanitizedLinkId} from IP: ${getRealIp(req)}`);

      return res.send(`
        <html>
          <head>
            <meta http-equiv="refresh" content="3;url=${encodeURI(process.env.AUTO_LOGIN_REDIRECT_URL)}">
            <meta name="robots" content="noindex">
            <style>
              body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #111827; font-family: 'Arial', sans-serif; }
              .notification { background-color: #1f2937; color: white; padding: 16px; border-radius: 8px; display: flex; flex-direction: column; align-items: center; gap: 12px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); max-width: 400px; width: 100%; }
              h1 { font-size: 2.25em; color: white; text-align: center; margin: 0; }
              .spinner { border: 4px solid rgba(255, 255, 255, 0.3); border-top: 4px solid #ffffff; border-radius: 50%; width: 24px; height: 24px; animation: spin 1s linear infinite; }
              @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
              @font-face {
                font-family: 'Minecraft';
                src: url('../font/MinecraftRegular-Bmg3.otf') format('opentype');
                font-weight: normal;
                font-style: normal;
                font-display: swap;
              }        
              h1, h2, h3, h4, h5, h6 {
                font-family: 'Minecraft', sans-serif; /* Apply Minecraft font to headers */
              }
            </style>
          </head>
          <body>
            <div class="notification">
              <span class="spinner"></span>
              <h1>Login Expired</h1>
              <h1>Redirecting...</h1>
            </div>
          </body>
        </html>
      `);
    }

    // Verify client consistency
    const isIpMatch = linkData.ip === getRealIp(req);
    const isUserAgentMatch = linkData.userAgent === (req.get('User-Agent') || 'Discord-Bot-Request');
    const isLocal = isLocalIp(getRealIp(req)) && isLocalIp(linkData.ip);
    const strictUserAgentCheck = process.env.STRICT_USER_AGENT_CHECK === 'true';

    if (strictUserAgentCheck && !isUserAgentMatch && !isLocal) {
      temporaryLinks.delete(sanitizedLinkId);
      console.log(
        `Suspicious login attempt for link: ${sanitizedLinkId} from IP: ${getRealIp(req)}, ` +
        `expected IP: ${linkData.ip}, isLocal: ${isLocal}, ` +
        `userAgentMatch: ${isUserAgentMatch}, ` +
        `expectedUserAgent: ${linkData.userAgent}, ` +
        `actualUserAgent: ${req.get('User-Agent') || 'Discord-Bot-Request'}`
      );
      return res.status(403).json({ error: 'Invalid session' });
    }

    if (!isUserAgentMatch) {
      console.log(
        `Non-critical user-agent mismatch for link: ${sanitizedLinkId} from IP: ${getRealIp(req)}, ` +
        `expectedUserAgent: ${linkData.userAgent}, ` +
        `actualUserAgent: ${req.get('User-Agent') || 'Discord-Bot-Request'}`
      );
    }

    temporaryLinks.delete(sanitizedLinkId);
    console.log(`Successful auto-login for username: ${linkData.username} from IP: ${getRealIp(req)}, userAgent: ${req.get('User-Agent') || 'Discord-Bot-Request'}`);

    // Secure API key storage with additional client-side security and debugging
    res.send(`
    <!DOCTYPE html>
    <html>
    <head>
      <title>Secure Auto Login</title>
      <meta name="robots" content="noindex">
      <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'nonce-${nonce}'">
      <meta http-equiv="refresh" content="2;url=/">
      <style>
        body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #111827; font-family: 'Arial', sans-serif; }
        .notification { background-color: #1f2937; color: white; padding: 16px; border-radius: 8px; display: flex; flex-direction: column; align-items: center; gap: 12px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); max-width: 400px; width: 100%; }
        h1 { font-size: 2.25em; color: white; text-align: center; margin: 0; }
        .spinner { border: 4px solid rgba(255, 255, 255, 0.3); border-top: 4px solid #ffffff; border-radius: 50%; width: 24px; height: 24px; animation: spin 1s linear infinite; }
        @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
        @font-face {
          font-family: 'Minecraft';
          src: url('../font/MinecraftRegular-Bmg3.otf') format('opentype');
          font-weight: normal;
          font-style: normal;
          font-display: swap;
        }        
        h1, h2, h3, h4, h5, h6 {
          font-family: 'Minecraft', sans-serif; /* Apply Minecraft font to headers */
        }
      </style>
    </head>
    <body>
      <div class="notification">
        <span class="spinner"></span>
        <h1>Securely logging in...</h1>
      </div>
      <script nonce="${nonce}">
        (function() {
          console.log('Auto-login script started');
          const apiKey = '${sanitizeHtml(linkData.apiKey)}';
          console.log('API key retrieved');
          try {
            localStorage.setItem('apiKey', apiKey);
            console.log('API key stored in localStorage');
            sessionStorage.setItem('sessionTimestamp', Date.now());
            console.log('Session timestamp stored');
            setTimeout(() => {
              window.location.href = '/';
              console.log('Redirect initiated to /');
            }, 2000);
          } catch (e) {
            console.error('Storage error:', e.message);
            setTimeout(() => {
              window.location.href = '${encodeURI(process.env.AUTO_LOGIN_REDIRECT_URL)}';
              console.log('Fallback redirect initiated to AUTO_LOGIN_REDIRECT_URL');
            }, 2000);
          }
        })();
      </script>
    </body>
    </html>
  `);
  });
}