/**
 * Token Manager for JWT access tokens and refresh tokens
 * Implements secure session management with token rotation
 */

const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const { hashValue, verifyHash, generateToken } = require('./encryption');

const ACCESS_TOKEN_TTL = 15 * 60; // 15 minutes in seconds
const REFRESH_TOKEN_TTL = 7 * 24 * 60 * 60; // 7 days in seconds
const REFRESH_TOKEN_BYTES = 64; // 128 character hex string

let jwtSecret = null;

/**
 * Initialize token manager with JWT secret
 * @param {string} secret - JWT signing secret
 */
function initTokenManager(secret) {
  if (!secret || typeof secret !== 'string') {
    throw new Error('JWT secret is required');
  }
  
  jwtSecret = secret;
  console.log('✅ Token manager initialized');
}

/**
 * Generate device fingerprint from request
 * @param {Object} req - HTTP request object
 * @returns {string} Device fingerprint (32 character hex)
 */
function generateDeviceFingerprint(req) {
  const components = [
    req.headers['user-agent'] || '',
    req.headers['accept-language'] || '',
    req.ip || req.connection?.remoteAddress || '',
    req.headers['x-forwarded-for'] || ''
  ];
  
  return crypto
    .createHash('sha256')
    .update(components.join('|'))
    .digest('hex')
    .substring(0, 32);
}

/**
 * Generate JWT access token
 * @param {Object} payload - Token payload (userId, email, role, plan)
 * @param {Object} options - Token options
 * @returns {string} JWT token
 */
function generateAccessToken(payload, options = {}) {
  if (!jwtSecret) {
    throw new Error('Token manager not initialized');
  }
  
  const jti = crypto.randomUUID();
  const now = Math.floor(Date.now() / 1000);
  
  const tokenPayload = {
    jti,
    userId: payload.userId,
    email: payload.email,
    role: payload.role || 'user',
    plan: payload.plan || 'hobby',
    iat: now,
    exp: now + (options.ttl || ACCESS_TOKEN_TTL)
  };
  
  return jwt.sign(tokenPayload, jwtSecret, {
    algorithm: 'HS256'
  });
}

/**
 * Verify and decode JWT access token
 * @param {string} token - JWT token to verify
 * @returns {Object|null} Decoded token payload or null if invalid
 */
function verifyAccessToken(token) {
  if (!jwtSecret) {
    throw new Error('Token manager not initialized');
  }
  
  try {
    const decoded = jwt.verify(token, jwtSecret, {
      algorithms: ['HS256']
    });
    
    return decoded;
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      return { expired: true, error: 'Token expired' };
    }
    if (error.name === 'JsonWebTokenError') {
      return { invalid: true, error: 'Invalid token' };
    }
    return null;
  }
}

/**
 * Generate refresh token
 * @returns {Object} Object with token and tokenHash
 */
function generateRefreshToken() {
  const token = generateToken(REFRESH_TOKEN_BYTES);
  const { hash, salt } = hashValue(token);
  
  return {
    token,
    tokenHash: `${salt}:${hash}`
  };
}

/**
 * Verify refresh token against stored hash
 * @param {string} token - Refresh token to verify
 * @param {string} storedHash - Stored hash in format "salt:hash"
 * @returns {boolean} True if token matches hash
 */
function verifyRefreshToken(token, storedHash) {
  if (!token || !storedHash) {
    return false;
  }
  
  try {
    const [salt, hash] = storedHash.split(':');
    if (!salt || !hash) {
      return false;
    }
    
    return verifyHash(token, hash, salt);
  } catch (error) {
    return false;
  }
}

/**
 * Extract token from Authorization header or cookie
 * @param {Object} req - HTTP request object
 * @param {string} cookieName - Name of the cookie containing token
 * @returns {string|null} Token or null
 */
function extractToken(req, cookieName = 'access_token') {
  // Check Authorization header first (Bearer token)
  const authHeader = req.headers.authorization;
  if (authHeader && authHeader.startsWith('Bearer ')) {
    return authHeader.substring(7);
  }
  
  // Check cookie
  if (req.headers.cookie) {
    const cookies = parseCookies(req.headers.cookie);
    return cookies[cookieName] || null;
  }
  
  return null;
}

/**
 * Parse cookie header
 * @param {string} cookieHeader - Cookie header string
 * @returns {Object} Parsed cookies
 */
function parseCookies(cookieHeader) {
  const cookies = {};
  
  if (!cookieHeader) {
    return cookies;
  }
  
  cookieHeader.split(';').forEach(cookie => {
    const [name, ...rest] = cookie.split('=');
    if (name && rest.length > 0) {
      cookies[name.trim()] = rest.join('=').trim();
    }
  });
  
  return cookies;
}

/**
 * Create secure cookie string
 * @param {string} name - Cookie name
 * @param {string} value - Cookie value
 * @param {Object} options - Cookie options
 * @returns {string} Set-Cookie header value
 */
function createSecureCookie(name, value, options = {}) {
  const parts = [`${name}=${value}`];
  
  if (options.maxAge) {
    parts.push(`Max-Age=${options.maxAge}`);
  }
  
  if (options.path) {
    parts.push(`Path=${options.path}`);
  } else {
    parts.push('Path=/');
  }
  
  if (options.httpOnly !== false) {
    parts.push('HttpOnly');
  }
  
  if (options.secure) {
    parts.push('Secure');
  }
  
  if (options.sameSite) {
    parts.push(`SameSite=${options.sameSite}`);
  } else {
    parts.push('SameSite=Strict');
  }
  
  return parts.join('; ');
}

/**
 * Get token TTL values
 * @returns {Object} Object with accessTokenTTL and refreshTokenTTL
 */
function getTokenTTL() {
  return {
    accessTokenTTL: ACCESS_TOKEN_TTL,
    refreshTokenTTL: REFRESH_TOKEN_TTL
  };
}

/**
 * Check if token manager is initialized
 * @returns {boolean}
 */
function isTokenManagerInitialized() {
  return jwtSecret !== null;
}

module.exports = {
  initTokenManager,
  generateDeviceFingerprint,
  generateAccessToken,
  verifyAccessToken,
  generateRefreshToken,
  verifyRefreshToken,
  extractToken,
  createSecureCookie,
  parseCookies,
  getTokenTTL,
  isTokenManagerInitialized
};
