import jwt from 'jsonwebtoken';
import crypto from 'crypto';
import { userDb, relayTokensDb } from '../database/db.js';
import { IS_PLATFORM } from '../constants/config.js';

let JWT_SECRET = process.env.JWT_SECRET?.trim();
if (!JWT_SECRET) {
  if (IS_PLATFORM) {
    // In local/self-hosted mode, generate a random secret (auth is bypassed anyway)
    JWT_SECRET = crypto.randomBytes(32).toString('hex');
  } else {
    console.error('[SECURITY] JWT_SECRET environment variable is required. Server cannot start without it.');
    process.exit(1);
  }
}

// Optional static API key middleware
const validateApiKey = (req, res, next) => {
  if (!process.env.API_KEY) return next();
  const apiKey = req.headers['x-api-key'];
  if (apiKey !== process.env.API_KEY.trim()) {
    return res.status(401).json({ error: 'Invalid API key' });
  }
  next();
};

// Extract JWT from request: cookie → Bearer header → query param (SSE only)
const extractToken = (req) => {
  // 1. httpOnly cookie (browser sessions — primary auth method)
  if (req.cookies?.session) return req.cookies.session;

  // 2. Bearer header (API clients, MCP)
  const authHeader = req.headers['authorization'];
  if (authHeader?.startsWith('Bearer ')) return authHeader.slice(7);

  // 3. Query param — for GET requests (SSE EventSource + iframe embedding)
  if (req.query?.token && req.method === 'GET') {
    return req.query.token;
  }

  return null;
};

// JWT authentication middleware
const authenticateToken = async (req, res, next) => {
  // Platform mode: use first database user
  if (IS_PLATFORM) {
    try {
      const user = await userDb.getFirstUser();
      if (!user) return res.status(500).json({ error: 'Platform mode: No user found in database' });
      req.user = user;
      return next();
    } catch (error) {
      console.error('Platform mode error:', error);
      return res.status(500).json({ error: 'Platform mode: Failed to fetch user' });
    }
  }

  const token = extractToken(req);
  if (!token) {
    return res.status(401).json({ error: 'Access denied. No token provided.' });
  }

  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    const user = await userDb.getUserById(decoded.userId);
    if (!user) return res.status(401).json({ error: 'Invalid token. User not found.' });
    req.user = user;
    // If token came from query param, set session cookie for subsequent requests (iframe auto-auth)
    if (req.query?.token && !req.cookies?.session) {
      res.cookie('session', token, COOKIE_OPTIONS);
    }
    next();
  } catch (error) {
    return res.status(403).json({ error: 'Invalid or expired token' });
  }
};

// Generate JWT token (30-day expiration)
const generateToken = (user) => {
  return jwt.sign(
    { userId: user.id, username: user.username },
    JWT_SECRET,
    { expiresIn: '30d' }
  );
};

// Cookie config for httpOnly session
// Works for both self-hosted (same origin) and split deploy (Vercel proxy → Railway)
const isSecureEnv = process.env.NODE_ENV === 'production' || !!process.env.VERCEL || !!process.env.RAILWAY_ENVIRONMENT;
const COOKIE_OPTIONS = {
  httpOnly: true,
  secure: isSecureEnv,
  // 'none' required for cross-origin iframe embedding (Vercel frontend → Railway backend)
  // 'strict' used in local/dev mode where everything is same-origin
  sameSite: isSecureEnv ? 'none' : 'strict',
  maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
  path: '/',
};

// Set session cookie on response
const setSessionCookie = (res, token) => {
  res.cookie('session', token, COOKIE_OPTIONS);
};

// Clear session cookie
const clearSessionCookie = (res) => {
  res.clearCookie('session', { path: '/' });
};

// WebSocket authentication (parse cookie from upgrade request headers)
const authenticateWebSocket = async (request) => {
  // Platform mode: bypass
  if (IS_PLATFORM) {
    try {
      const user = await userDb.getFirstUser();
      return user ? { userId: user.id, username: user.username } : null;
    } catch { return null; }
  }

  let token = null;

  // 1. Parse cookie from upgrade request
  const cookieHeader = request.headers?.cookie || '';
  if (cookieHeader) {
    const cookies = Object.fromEntries(
      cookieHeader.split(';').map(c => {
        const [k, ...v] = c.trim().split('=');
        return [k, v.join('=')];
      })
    );
    token = cookies.session || null;
  }

  // 2. Fallback: query param (legacy)
  if (!token) {
    try {
      const url = new URL(request.url, 'http://localhost');
      token = url.searchParams.get('token');
    } catch { /* ignore */ }
  }

  if (!token) {
    return null;
  }

  // Relay token (upfyn_ prefix) — validate against DB, not JWT
  if (token.startsWith('upfyn_') || token.startsWith('rt_')) {
    try {
      // Timeout relay token validation to prevent hung WebSocket upgrades when Turso is slow
      const tokenData = await Promise.race([
        relayTokensDb.validateToken(token),
        new Promise((_, reject) => setTimeout(() => reject(new Error('Token validation timeout')), 10000))
      ]);
      if (tokenData) {
        return { userId: Number(tokenData.user_id), username: tokenData.username };
      }
    } catch (err) {
      console.warn('[WS] Relay token validation failed:', err.message);
    }
    return null;
  }

  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    // Validate against Turso — DB is source of truth
    const user = await userDb.getUserById(decoded.userId);
    if (!user) return null;
    return { userId: user.id, username: user.username };
  } catch { return null; }
};

export {
  validateApiKey,
  authenticateToken,
  generateToken,
  authenticateWebSocket,
  setSessionCookie,
  clearSessionCookie,
  JWT_SECRET
};
