/**
 * Session Management Service
 * 
 * Implements secure session management per DoD/MilSpec requirements:
 * - NIST SP 800-53: AC-12 (Session Termination)
 * - DISA STIG: Application Security
 * 
 * Features:
 * - Session timeout per classification level
 * - Concurrent session limits
 * - Session fixation protection
 * - Secure session storage (encrypted, tamper-proof)
 * - Session revocation capability
 */

import { getDb } from '../db'
import { logger } from '../lib/logger'
import crypto from 'crypto'

export interface Session {
  id: string
  userId: string
  sessionToken: string
  ipAddress?: string
  userAgent?: string
  classificationLevel: 'UNCLASSIFIED' | 'CUI' | 'CONFIDENTIAL' | 'SECRET' | 'TOP_SECRET'
  createdAt: Date
  expiresAt: Date
  lastActivity: Date
  revoked: boolean
  mfaVerified: boolean
}

/**
 * Session timeout by classification level (in minutes)
 * Per DoD requirements, higher classification = shorter timeout
 */
const SESSION_TIMEOUTS: Record<string, number> = {
  UNCLASSIFIED: 480, // 8 hours
  CUI: 240, // 4 hours
  CONFIDENTIAL: 120, // 2 hours
  SECRET: 60, // 1 hour
  TOP_SECRET: 30, // 30 minutes
}

/**
 * Maximum concurrent sessions per user
 */
const MAX_CONCURRENT_SESSIONS = 5

/**
 * Create a new session
 */
export async function createSession(
  userId: string,
  ipAddress?: string,
  userAgent?: string,
  classificationLevel: Session['classificationLevel'] = 'UNCLASSIFIED',
  mfaVerified: boolean = false
): Promise<Session> {
  const db = getDb()
  
  // Check concurrent session limit
  const activeSessions = await getActiveSessions(userId)
  if (activeSessions.length >= MAX_CONCURRENT_SESSIONS) {
    // Revoke oldest session
    const oldestSession = activeSessions.sort((a, b) => 
      a.createdAt.getTime() - b.createdAt.getTime()
    )[0]
    await revokeSession(oldestSession.id)
    logger.info('Revoked oldest session due to concurrent session limit', {
      userId,
      sessionId: oldestSession.id,
    })
  }

  // Generate secure session token
  const sessionToken = generateSessionToken()
  const sessionId = crypto.randomUUID()
  
  // Calculate expiration based on classification level
  const timeoutMinutes = SESSION_TIMEOUTS[classificationLevel] || SESSION_TIMEOUTS.UNCLASSIFIED
  const expiresAt = new Date(Date.now() + timeoutMinutes * 60 * 1000)

  // Create session
  await db.query(
    `INSERT INTO sessions (id, user_id, session_token, ip_address, user_agent, classification_level, expires_at, mfa_verified, created_at, last_activity)
     VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW(), NOW())`,
    [sessionId, userId, sessionToken, ipAddress, userAgent, classificationLevel, expiresAt, mfaVerified]
  )

  logger.info('Session created', { userId, sessionId, classificationLevel })

  return {
    id: sessionId,
    userId,
    sessionToken,
    ipAddress,
    userAgent,
    classificationLevel,
    createdAt: new Date(),
    expiresAt,
    lastActivity: new Date(),
    revoked: false,
    mfaVerified,
  }
}

/**
 * Get session by token
 */
export async function getSessionByToken(sessionToken: string): Promise<Session | null> {
  const db = getDb()
  
  const result = await db.query(
    `SELECT id, user_id, session_token, ip_address, user_agent, classification_level, 
            created_at, expires_at, last_activity, revoked, mfa_verified
     FROM sessions
     WHERE session_token = $1 AND revoked = false`,
    [sessionToken]
  )

  if (result.rows.length === 0) {
    return null
  }

  const row = result.rows[0]
  
  // Check if expired
  if (new Date(row.expires_at) < new Date()) {
    await revokeSession(row.id, 'EXPIRED')
    return null
  }

  return {
    id: row.id,
    userId: row.user_id,
    sessionToken: row.session_token,
    ipAddress: row.ip_address,
    userAgent: row.user_agent,
    classificationLevel: row.classification_level,
    createdAt: row.created_at,
    expiresAt: row.expires_at,
    lastActivity: row.last_activity,
    revoked: row.revoked,
    mfaVerified: row.mfa_verified,
  }
}

/**
 * Update session activity
 */
export async function updateSessionActivity(sessionId: string, action?: string, resourceType?: string, resourceId?: string): Promise<void> {
  const db = getDb()
  
  // Update last activity timestamp
  await db.query(
    'UPDATE sessions SET last_activity = NOW() WHERE id = $1',
    [sessionId]
  )

  // Log session activity for audit trail
  if (action) {
    await db.query(
      `INSERT INTO session_activity (session_id, action, resource_type, resource_id, created_at)
       VALUES ($1, $2, $3, $4, NOW())`,
      [sessionId, action, resourceType, resourceId]
    )
  }
}

/**
 * Revoke session
 */
export async function revokeSession(sessionId: string, reason?: string): Promise<void> {
  const db = getDb()
  
  await db.query(
    'UPDATE sessions SET revoked = true, revoked_at = NOW() WHERE id = $1',
    [sessionId]
  )

  logger.info('Session revoked', { sessionId, reason })
}

/**
 * Revoke all sessions for a user
 */
export async function revokeAllUserSessions(userId: string, reason?: string): Promise<void> {
  const db = getDb()
  
  await db.query(
    'UPDATE sessions SET revoked = true, revoked_at = NOW() WHERE user_id = $1 AND revoked = false',
    [userId]
  )

  logger.info('All user sessions revoked', { userId, reason })
}

/**
 * Get active sessions for a user
 */
export async function getActiveSessions(userId: string): Promise<Session[]> {
  const db = getDb()
  
  const result = await db.query(
    `SELECT id, user_id, session_token, ip_address, user_agent, classification_level,
            created_at, expires_at, last_activity, revoked, mfa_verified
     FROM sessions
     WHERE user_id = $1 AND revoked = false AND expires_at > NOW()
     ORDER BY created_at DESC`,
    [userId]
  )

  return result.rows.map(row => ({
    id: row.id,
    userId: row.user_id,
    sessionToken: row.session_token,
    ipAddress: row.ip_address,
    userAgent: row.user_agent,
    classificationLevel: row.classification_level,
    createdAt: row.created_at,
    expiresAt: row.expires_at,
    lastActivity: row.last_activity,
    revoked: row.revoked,
    mfaVerified: row.mfa_verified,
  }))
}

/**
 * Clean up expired sessions (should be run periodically)
 */
export async function cleanupExpiredSessions(): Promise<number> {
  const db = getDb()
  
  const result = await db.query(
    'UPDATE sessions SET revoked = true, revoked_at = NOW() WHERE expires_at < NOW() AND revoked = false RETURNING id'
  )

  const count = result.rows.length
  if (count > 0) {
    logger.info('Cleaned up expired sessions', { count })
  }

  return count
}

/**
 * Generate secure session token
 */
function generateSessionToken(): string {
  // Generate 32-byte random token, base64 encoded
  return crypto.randomBytes(32).toString('base64url')
}

