/**
 * Authentication Manager for DashCaddy
 * Handles JWT tokens and API key generation/validation
 * Provides defense-in-depth alongside Caddy forward_auth
 */

const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const credentialManager = require('./credential-manager');
const cryptoUtils = require('../security/crypto-utils');

// JWT signing secret - derived from encryption key for consistency
const JWT_SECRET = cryptoUtils.loadOrCreateKey();

// Namespace for API keys in credential manager
const API_KEY_NAMESPACE = 'auth.apikey';
const API_KEY_METADATA_NAMESPACE = 'auth.metadata';

class AuthManager {
  constructor() {
    this.keyMetadataCache = new Map(); // Cache for API key metadata
    console.log('[AuthManager] Initialized');
  }

  /**
   * Generate JWT token
   * @param {Object} payload - Token payload (must include sub: userId)
   * @param {string} expiresIn - Expiration time (default: '24h')
   * @returns {Promise<string>} JWT token
   */
  async generateJWT(payload, expiresIn = '24h') {
    try {
      if (!payload.sub) {
        throw new Error('JWT payload must include "sub" (subject/userId)');
      }

      const token = jwt.sign(
        {
          ...payload,
          iat: Math.floor(Date.now() / 1000),
          scope: payload.scope || ['read', 'write']
        },
        JWT_SECRET,
        { expiresIn }
      );

      console.log(`[AuthManager] Generated JWT for user: ${payload.sub}, expires in: ${expiresIn}`);
      return token;
    } catch (error) {
      console.error('[AuthManager] JWT generation failed:', error.message);
      throw error;
    }
  }

  /**
   * Verify JWT token
   * @param {string} token - JWT token to verify
   * @returns {Promise<Object|null>} Decoded payload or null if invalid
   */
  async verifyJWT(token) {
    try {
      const decoded = jwt.verify(token, JWT_SECRET);
      return {
        userId: decoded.sub,
        scope: decoded.scope || [],
        iat: decoded.iat,
        exp: decoded.exp
      };
    } catch (error) {
      if (error.name === 'TokenExpiredError') {
        console.log('[AuthManager] JWT token expired');
      } else if (error.name === 'JsonWebTokenError') {
        console.log('[AuthManager] JWT token invalid:', error.message);
      } else {
        console.error('[AuthManager] JWT verification failed:', error.message);
      }
      return null;
    }
  }

  /**
   * Generate API key
   * @param {string} name - Human-readable name for the key
   * @param {Array<string>} scopes - Permission scopes (default: ['read', 'write'])
   * @returns {Promise<Object>} { key, id, name, scopes, createdAt }
   */
  async generateAPIKey(name, scopes = ['read', 'write']) {
    try {
      if (!name || typeof name !== 'string') {
        throw new Error('API key name is required');
      }

      // Generate secure random key (32 bytes = 64 hex chars)
      const keyId = crypto.randomBytes(16).toString('hex');
      const keySecret = crypto.randomBytes(32).toString('hex');
      const apiKey = `dk_${keyId}_${keySecret}`; // dk = DashCaddy Key

      // Store key hash (not the key itself) in credential manager
      const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
      const credentialKey = `${API_KEY_NAMESPACE}.${keyId}`;

      await credentialManager.store(credentialKey, keyHash);

      // Store metadata separately (non-sensitive)
      const metadata = {
        id: keyId,
        name,
        scopes,
        createdAt: new Date().toISOString(),
        lastUsed: null
      };

      const metadataKey = `${API_KEY_METADATA_NAMESPACE}.${keyId}`;
      await credentialManager.store(metadataKey, JSON.stringify(metadata));

      // Cache metadata
      this.keyMetadataCache.set(keyId, metadata);

      console.log(`[AuthManager] Generated API key: ${name} (${keyId})`);

      return {
        key: apiKey,
        id: keyId,
        name,
        scopes,
        createdAt: metadata.createdAt
      };
    } catch (error) {
      console.error('[AuthManager] API key generation failed:', error.message);
      throw error;
    }
  }

  /**
   * Verify API key
   * @param {string} key - API key to verify
   * @returns {Promise<Object|null>} { keyId, scopes, name } or null if invalid
   */
  async verifyAPIKey(key) {
    try {
      // Parse key format: dk_<keyId>_<secret>
      if (!key || !key.startsWith('dk_')) {
        return null;
      }

      const parts = key.split('_');
      if (parts.length !== 3) {
        return null;
      }

      const keyId = parts[1];
      const credentialKey = `${API_KEY_NAMESPACE}.${keyId}`;

      // Retrieve stored hash
      const storedHash = await credentialManager.retrieve(credentialKey);
      if (!storedHash) {
        console.log(`[AuthManager] API key not found: ${keyId}`);
        return null;
      }

      // Verify key matches stored hash
      const providedHash = crypto.createHash('sha256').update(key).digest('hex');
      if (!crypto.timingSafeEqual(Buffer.from(storedHash), Buffer.from(providedHash))) {
        console.log(`[AuthManager] API key hash mismatch: ${keyId}`);
        return null;
      }

      // Get metadata
      const metadata = await this.getKeyMetadata(keyId);
      if (!metadata) {
        console.log(`[AuthManager] API key metadata not found: ${keyId}`);
        return null;
      }

      // Update last used timestamp (non-blocking)
      this.updateLastUsed(keyId, metadata).catch(err =>
        console.error(`[AuthManager] Failed to update lastUsed for ${keyId}:`, err.message)
      );

      console.log(`[AuthManager] API key verified: ${metadata.name} (${keyId})`);

      return {
        keyId,
        scopes: metadata.scopes || [],
        name: metadata.name
      };
    } catch (error) {
      console.error('[AuthManager] API key verification failed:', error.message);
      return null;
    }
  }

  /**
   * Revoke API key
   * @param {string} keyId - Key ID to revoke
   * @returns {Promise<boolean>} Success status
   */
  async revokeAPIKey(keyId) {
    try {
      const credentialKey = `${API_KEY_NAMESPACE}.${keyId}`;
      const metadataKey = `${API_KEY_METADATA_NAMESPACE}.${keyId}`;

      await credentialManager.delete(credentialKey);
      await credentialManager.delete(metadataKey);

      this.keyMetadataCache.delete(keyId);

      console.log(`[AuthManager] Revoked API key: ${keyId}`);
      return true;
    } catch (error) {
      console.error(`[AuthManager] Failed to revoke API key ${keyId}:`, error.message);
      return false;
    }
  }

  /**
   * List all API keys (returns metadata, not actual keys)
   * @returns {Promise<Array<Object>>} Array of API key metadata
   */
  async listAPIKeys() {
    try {
      const allKeys = await credentialManager.list();
      const metadataKeys = allKeys.filter(k => k.startsWith(API_KEY_METADATA_NAMESPACE));

      const keys = [];
      for (const metaKey of metadataKeys) {
        const keyId = metaKey.replace(`${API_KEY_METADATA_NAMESPACE}.`, '');
        const metadata = await this.getKeyMetadata(keyId);
        if (metadata) {
          keys.push(metadata);
        }
      }

      return keys;
    } catch (error) {
      console.error('[AuthManager] Failed to list API keys:', error.message);
      return [];
    }
  }

  /**
   * Get metadata for a specific API key
   * @param {string} keyId - Key ID
   * @returns {Promise<Object|null>} Metadata or null
   */
  async getKeyMetadata(keyId) {
    try {
      // Check cache first
      if (this.keyMetadataCache.has(keyId)) {
        return this.keyMetadataCache.get(keyId);
      }

      const metadataKey = `${API_KEY_METADATA_NAMESPACE}.${keyId}`;
      const metadataJson = await credentialManager.retrieve(metadataKey);

      if (!metadataJson) {
        return null;
      }

      const metadata = JSON.parse(metadataJson);
      this.keyMetadataCache.set(keyId, metadata);

      return metadata;
    } catch (error) {
      console.error(`[AuthManager] Failed to get metadata for ${keyId}:`, error.message);
      return null;
    }
  }

  /**
   * Update last used timestamp for API key
   * @param {string} keyId - Key ID
   * @param {Object} metadata - Current metadata
   * @returns {Promise<void>}
   */
  async updateLastUsed(keyId, metadata) {
    try {
      const updatedMetadata = {
        ...metadata,
        lastUsed: new Date().toISOString()
      };

      const metadataKey = `${API_KEY_METADATA_NAMESPACE}.${keyId}`;
      await credentialManager.store(metadataKey, JSON.stringify(updatedMetadata));

      this.keyMetadataCache.set(keyId, updatedMetadata);
    } catch (error) {
      console.error(`[AuthManager] Failed to update lastUsed for ${keyId}:`, error.message);
    }
  }

  /**
   * Clear metadata cache (useful for testing or cache invalidation)
   */
  clearCache() {
    this.keyMetadataCache.clear();
    console.log('[AuthManager] Cache cleared');
  }
}

// Export singleton instance
module.exports = new AuthManager();
