const crypto = require("crypto");
const pool = require("../db/pool");
const { SESSION_TTL_DAYS } = require("../utils/session-cookie");

const INSERT_SESSION_SQL = `INSERT INTO sessions (id, user_id, expires_at, user_agent)
     VALUES ($1, $2, NOW() + ($3 || ' days')::interval, $4)
     RETURNING id, user_id, created_at, expires_at`;
const SELECT_ACTIVE_SESSION_SQL = `SELECT
      s.id,
      s.user_id,
      s.expires_at,
      u.username,
      u.role
    FROM sessions s
    JOIN users u ON u.id = s.user_id
    WHERE s.id = $1
      AND s.expires_at > NOW()`;

let ensureSessionsTablePromise = null;

function generateSessionId() {
  if (typeof crypto.randomUUID === "function") {
    return crypto.randomUUID().replace(/-/g, "") + crypto.randomBytes(8).toString("hex");
  }
  return crypto.randomBytes(32).toString("hex");
}

function isUndefinedTableError(error) {
  return error && error.code === "42P01";
}

async function ensureSessionsTable() {
  if (!ensureSessionsTablePromise) {
    ensureSessionsTablePromise = (async () => {
      await pool.query(`CREATE TABLE IF NOT EXISTS sessions (
  id VARCHAR(128) PRIMARY KEY,
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  expires_at TIMESTAMPTZ NOT NULL,
  last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  user_agent TEXT
);`);
      await pool.query(
        "CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);"
      );
      await pool.query(
        "CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);"
      );
    })().catch((error) => {
      ensureSessionsTablePromise = null;
      throw error;
    });
  }

  await ensureSessionsTablePromise;
}

async function insertSession(id, userId, userAgent) {
  const result = await pool.query(INSERT_SESSION_SQL, [
    id,
    userId,
    String(SESSION_TTL_DAYS),
    userAgent,
  ]);
  return result.rows[0];
}

exports.createSession = async (userId, userAgent = null) => {
  const id = generateSessionId();
  try {
    return await insertSession(id, userId, userAgent);
  } catch (error) {
    if (!isUndefinedTableError(error)) {
      throw error;
    }

    await ensureSessionsTable();
    return insertSession(id, userId, userAgent);
  }
};

exports.getActiveSessionWithUser = async (sessionId) => {
  let result;
  try {
    result = await pool.query(SELECT_ACTIVE_SESSION_SQL, [sessionId]);
  } catch (error) {
    if (isUndefinedTableError(error)) {
      return null;
    }
    throw error;
  }

  const session = result.rows[0] || null;
  if (!session) return null;

  try {
    await pool.query(
      `UPDATE sessions
     SET last_seen_at = NOW()
     WHERE id = $1`,
      [sessionId]
    );
  } catch (error) {
    if (!isUndefinedTableError(error)) {
      throw error;
    }
  }

  return session;
};

exports.deleteSession = async (sessionId) => {
  try {
    await pool.query(
      `DELETE FROM sessions WHERE id = $1`,
      [sessionId]
    );
  } catch (error) {
    if (!isUndefinedTableError(error)) {
      throw error;
    }
  }
};
