/**
 * Session Repository - Data access layer for sessions and refresh tokens
 */

const { getDatabase } = require('../database/connection');
const crypto = require('crypto');

/**
 * Create a new session
 * @param {Object} sessionData - Session data
 * @returns {Object} Created session
 */
function createSession(sessionData) {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const now = Date.now();
  const id = sessionData.id || crypto.randomUUID();
  
  const stmt = db.prepare(`
    INSERT INTO sessions (
      id, user_id, token, refresh_token_hash, device_fingerprint,
      ip_address, user_agent, expires_at, created_at, last_accessed_at
    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `);
  
  stmt.run(
    id,
    sessionData.userId,
    sessionData.token,
    sessionData.refreshTokenHash || null,
    sessionData.deviceFingerprint || null,
    sessionData.ipAddress || null,
    sessionData.userAgent || null,
    sessionData.expiresAt,
    now,
    now
  );
  
  return getSessionById(id);
}

/**
 * Get session by ID
 * @param {string} sessionId - Session ID
 * @returns {Object|null} Session object or null
 */
function getSessionById(sessionId) {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const stmt = db.prepare('SELECT * FROM sessions WHERE id = ?');
  const row = stmt.get(sessionId);
  
  return row ? deserializeSession(row) : null;
}

/**
 * Get session by token
 * @param {string} token - Session token
 * @returns {Object|null} Session object or null
 */
function getSessionByToken(token) {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const stmt = db.prepare('SELECT * FROM sessions WHERE token = ?');
  const row = stmt.get(token);
  
  return row ? deserializeSession(row) : null;
}

/**
 * Get all sessions for a user
 * @param {string} userId - User ID
 * @returns {Array} Array of sessions
 */
function getSessionsByUserId(userId) {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const stmt = db.prepare(`
    SELECT * FROM sessions 
    WHERE user_id = ? AND expires_at > ?
    ORDER BY last_accessed_at DESC
  `);
  const rows = stmt.all(userId, Date.now());
  
  return rows.map(deserializeSession);
}

/**
 * Update session
 * @param {string} sessionId - Session ID
 * @param {Object} updates - Fields to update
 * @returns {Object|null} Updated session
 */
function updateSession(sessionId, updates) {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const sets = [];
  const values = [];
  
  const fields = ['last_accessed_at', 'expires_at', 'refresh_token_hash'];
  
  fields.forEach(field => {
    if (updates.hasOwnProperty(field)) {
      sets.push(`${field} = ?`);
      values.push(updates[field]);
    }
  });
  
  if (sets.length === 0) {
    return getSessionById(sessionId);
  }
  
  values.push(sessionId);
  
  const sql = `UPDATE sessions SET ${sets.join(', ')} WHERE id = ?`;
  const stmt = db.prepare(sql);
  stmt.run(...values);
  
  return getSessionById(sessionId);
}

/**
 * Delete session (logout)
 * @param {string} sessionId - Session ID
 * @returns {boolean} True if deleted
 */
function deleteSession(sessionId) {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const stmt = db.prepare('DELETE FROM sessions WHERE id = ?');
  const result = stmt.run(sessionId);
  
  return result.changes > 0;
}

/**
 * Delete all sessions for a user (logout all)
 * @param {string} userId - User ID
 * @returns {number} Number of sessions deleted
 */
function deleteAllUserSessions(userId) {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const stmt = db.prepare('DELETE FROM sessions WHERE user_id = ?');
  const result = stmt.run(userId);
  
  return result.changes;
}

/**
 * Clean up expired sessions
 * @returns {number} Number of sessions deleted
 */
function cleanupExpiredSessions() {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const stmt = db.prepare('DELETE FROM sessions WHERE expires_at <= ?');
  const result = stmt.run(Date.now());
  
  return result.changes;
}

/**
 * Create a refresh token
 * @param {Object} tokenData - Refresh token data
 * @returns {Object} Created refresh token
 */
function createRefreshToken(tokenData) {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const id = tokenData.id || crypto.randomUUID();
  const now = Date.now();
  
  const stmt = db.prepare(`
    INSERT INTO refresh_tokens (
      id, user_id, session_id, token_hash, device_fingerprint,
      ip_address, user_agent, expires_at, created_at
    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
  `);
  
  stmt.run(
    id,
    tokenData.userId,
    tokenData.sessionId,
    tokenData.tokenHash,
    tokenData.deviceFingerprint,
    tokenData.ipAddress || null,
    tokenData.userAgent || null,
    tokenData.expiresAt,
    now
  );
  
  return getRefreshTokenById(id);
}

/**
 * Get refresh token by ID
 * @param {string} tokenId - Token ID
 * @returns {Object|null} Refresh token or null
 */
function getRefreshTokenById(tokenId) {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const stmt = db.prepare('SELECT * FROM refresh_tokens WHERE id = ?');
  const row = stmt.get(tokenId);
  
  return row ? deserializeRefreshToken(row) : null;
}

/**
 * Get refresh token by hash
 * @param {string} tokenHash - Token hash
 * @returns {Object|null} Refresh token or null
 */
function getRefreshTokenByHash(tokenHash) {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const stmt = db.prepare(`
    SELECT * FROM refresh_tokens 
    WHERE token_hash = ? AND used = 0 AND revoked = 0 AND expires_at > ?
  `);
  const row = stmt.get(tokenHash, Date.now());
  
  return row ? deserializeRefreshToken(row) : null;
}

/**
 * Mark refresh token as used
 * @param {string} tokenId - Token ID
 * @returns {boolean} True if updated
 */
function markRefreshTokenUsed(tokenId) {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const stmt = db.prepare('UPDATE refresh_tokens SET used = 1, used_at = ? WHERE id = ?');
  const result = stmt.run(Date.now(), tokenId);
  
  return result.changes > 0;
}

/**
 * Revoke refresh token
 * @param {string} tokenId - Token ID
 * @returns {boolean} True if revoked
 */
function revokeRefreshToken(tokenId) {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const stmt = db.prepare('UPDATE refresh_tokens SET revoked = 1 WHERE id = ?');
  const result = stmt.run(tokenId);
  
  return result.changes > 0;
}

/**
 * Revoke all refresh tokens for a session
 * @param {string} sessionId - Session ID
 * @returns {number} Number of tokens revoked
 */
function revokeSessionRefreshTokens(sessionId) {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const stmt = db.prepare('UPDATE refresh_tokens SET revoked = 1 WHERE session_id = ?');
  const result = stmt.run(sessionId);
  
  return result.changes;
}

/**
 * Revoke all refresh tokens for a user
 * @param {string} userId - User ID
 * @returns {number} Number of tokens revoked
 */
function revokeAllUserRefreshTokens(userId) {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const stmt = db.prepare('UPDATE refresh_tokens SET revoked = 1 WHERE user_id = ?');
  const result = stmt.run(userId);
  
  return result.changes;
}

/**
 * Add token to blacklist
 * @param {Object} tokenData - Token data (jti, userId, expiresAt, reason)
 * @returns {Object} Created blacklist entry
 */
function addToBlacklist(tokenData) {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const id = crypto.randomUUID();
  
  const stmt = db.prepare(`
    INSERT INTO token_blacklist (id, token_jti, user_id, expires_at, created_at, reason)
    VALUES (?, ?, ?, ?, ?, ?)
  `);
  
  stmt.run(
    id,
    tokenData.jti,
    tokenData.userId,
    tokenData.expiresAt,
    Date.now(),
    tokenData.reason || null
  );
  
  return { id, ...tokenData };
}

/**
 * Check if token is blacklisted
 * @param {string} jti - JWT ID
 * @returns {boolean} True if blacklisted
 */
function isTokenBlacklisted(jti) {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const stmt = db.prepare('SELECT COUNT(*) as count FROM token_blacklist WHERE token_jti = ?');
  const result = stmt.get(jti);
  
  return result.count > 0;
}

/**
 * Clean up expired blacklist entries
 * @returns {number} Number of entries deleted
 */
function cleanupExpiredBlacklist() {
  const db = getDatabase();
  if (!db) {
    throw new Error('Database not initialized');
  }
  
  const stmt = db.prepare('DELETE FROM token_blacklist WHERE expires_at <= ?');
  const result = stmt.run(Date.now());
  
  return result.changes;
}

function deserializeSession(row) {
  if (!row) {
    return null;
  }
  
  return {
    id: row.id,
    userId: row.user_id,
    token: row.token,
    refreshTokenHash: row.refresh_token_hash,
    deviceFingerprint: row.device_fingerprint,
    ipAddress: row.ip_address,
    userAgent: row.user_agent,
    expiresAt: row.expires_at,
    createdAt: row.created_at,
    lastAccessedAt: row.last_accessed_at
  };
}

function deserializeRefreshToken(row) {
  if (!row) {
    return null;
  }
  
  return {
    id: row.id,
    userId: row.user_id,
    sessionId: row.session_id,
    tokenHash: row.token_hash,
    deviceFingerprint: row.device_fingerprint,
    ipAddress: row.ip_address,
    userAgent: row.user_agent,
    used: Boolean(row.used),
    revoked: Boolean(row.revoked),
    expiresAt: row.expires_at,
    createdAt: row.created_at,
    usedAt: row.used_at
  };
}

module.exports = {
  createSession,
  getSessionById,
  getSessionByToken,
  getSessionsByUserId,
  updateSession,
  deleteSession,
  deleteAllUserSessions,
  cleanupExpiredSessions,
  createRefreshToken,
  getRefreshTokenById,
  getRefreshTokenByHash,
  markRefreshTokenUsed,
  revokeRefreshToken,
  revokeSessionRefreshTokens,
  revokeAllUserRefreshTokens,
  addToBlacklist,
  isTokenBlacklisted,
  cleanupExpiredBlacklist
};
