const crypto = require('crypto');
const env = require('../config/env');

const toHex = (buffer) => Buffer.from(buffer).toString('hex');

const hashPassword = (password, saltHex = toHex(crypto.randomBytes(16))) => {
  const hash = crypto.scryptSync(password, saltHex, 64);
  return `${saltHex}:${toHex(hash)}`;
};

const verifyPassword = (password, encoded) => {
  if (!encoded || typeof encoded !== 'string' || !encoded.includes(':')) return false;
  const [saltHex, expectedHex] = encoded.split(':');
  if (!saltHex || !expectedHex) return false;
  const calculated = crypto.scryptSync(password, saltHex, 64);
  const expected = Buffer.from(expectedHex, 'hex');
  if (calculated.length !== expected.length) return false;
  return crypto.timingSafeEqual(calculated, expected);
};

const hashToken = (token) =>
  crypto.createHash('sha256').update(`${env.AUTH_TOKEN_SECRET}:${token}`).digest('hex');

const sanitizeUser = (row) => ({
  id: row.id,
  username: row.username,
  fullName: row.full_name,
  role: row.role
});

const ensureAuthTables = async (pool) => {
  await pool.query(`
    CREATE TABLE IF NOT EXISTS app_users (
      id bigserial PRIMARY KEY,
      username text NOT NULL UNIQUE,
      full_name text NOT NULL,
      role text NOT NULL DEFAULT 'ADMIN',
      password_hash text NOT NULL,
      is_active boolean NOT NULL DEFAULT true,
      created_at timestamptz NOT NULL DEFAULT NOW(),
      updated_at timestamptz NOT NULL DEFAULT NOW()
    );
  `);

  await pool.query(`
    CREATE TABLE IF NOT EXISTS app_sessions (
      token_hash text PRIMARY KEY,
      user_id bigint NOT NULL REFERENCES app_users(id) ON DELETE CASCADE,
      expires_at timestamptz NOT NULL,
      created_at timestamptz NOT NULL DEFAULT NOW(),
      last_used_at timestamptz NOT NULL DEFAULT NOW(),
      user_agent text,
      ip_address text
    );
  `);

  await pool.query(
    'CREATE INDEX IF NOT EXISTS idx_app_sessions_user_id ON app_sessions (user_id);'
  );
  await pool.query(
    'CREATE INDEX IF NOT EXISTS idx_app_sessions_expires_at ON app_sessions (expires_at);'
  );
};

const ensureDefaultAdmin = async (pool) => {
  const username = String(env.ADMIN_USERNAME || '').trim().toLowerCase();
  if (!username) {
    throw new Error('ADMIN_USERNAME no puede estar vacio.');
  }

  const fullName = String(env.ADMIN_FULL_NAME || 'Administrador INVIMA').trim();
  const passwordHash = hashPassword(String(env.ADMIN_PASSWORD || 'Admin123*'));

  const existing = await pool.query(
    `
      SELECT id, username, full_name, role, is_active
      FROM app_users
      WHERE lower(username) = $1
      LIMIT 1;
    `,
    [username]
  );

  if (!existing.rows.length) {
    const created = await pool.query(
      `
        INSERT INTO app_users (username, full_name, role, password_hash, is_active)
        VALUES ($1, $2, 'ADMIN', $3, true)
        RETURNING id, username, full_name, role;
      `,
      [username, fullName, passwordHash]
    );
    return { created: true, user: sanitizeUser(created.rows[0]) };
  }

  const updated = await pool.query(
    `
      UPDATE app_users
      SET full_name = $2, role = 'ADMIN', is_active = true, updated_at = NOW()
      WHERE id = $1
      RETURNING id, username, full_name, role;
    `,
    [existing.rows[0].id, fullName]
  );

  return { created: false, user: sanitizeUser(updated.rows[0]) };
};

const createSession = async (pool, user, meta = {}) => {
  const token = crypto.randomBytes(32).toString('base64url');
  const tokenHash = hashToken(token);
  const expiresAt = new Date(Date.now() + env.AUTH_SESSION_HOURS * 60 * 60 * 1000);

  await pool.query(
    `
      INSERT INTO app_sessions (token_hash, user_id, expires_at, user_agent, ip_address)
      VALUES ($1, $2, $3, $4, $5);
    `,
    [tokenHash, user.id, expiresAt, meta.userAgent || '', meta.ipAddress || '']
  );

  return {
    accessToken: token,
    expiresAt: expiresAt.toISOString()
  };
};

const login = async (pool, { identifier, password }, meta = {}) => {
  const normalized = String(identifier || '').trim().toLowerCase();
  if (!normalized || !password) {
    throw Object.assign(new Error('Credenciales invalidas.'), { status: 400 });
  }

  const { rows } = await pool.query(
    `
      SELECT id, username, full_name, role, password_hash, is_active
      FROM app_users
      WHERE lower(username) = $1
      LIMIT 1;
    `,
    [normalized]
  );

  const user = rows[0];
  if (!user || !user.is_active || !verifyPassword(password, user.password_hash)) {
    throw Object.assign(new Error('Usuario o contrasena incorrectos.'), { status: 401 });
  }

  const session = await createSession(pool, user, meta);
  return {
    user: sanitizeUser(user),
    ...session
  };
};

const authenticateToken = async (pool, token, options = {}) => {
  if (!token || typeof token !== 'string') return null;
  const tokenHash = hashToken(token);
  const { rows } = await pool.query(
    `
      SELECT
        s.user_id,
        s.expires_at,
        u.id,
        u.username,
        u.full_name,
        u.role,
        u.is_active
      FROM app_sessions s
      INNER JOIN app_users u ON u.id = s.user_id
      WHERE s.token_hash = $1
        AND s.expires_at > NOW()
      LIMIT 1;
    `,
    [tokenHash]
  );

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

  if (options.touch !== false) {
    await pool.query('UPDATE app_sessions SET last_used_at = NOW() WHERE token_hash = $1', [tokenHash]);
  }

  return sanitizeUser(session);
};

const logout = async (pool, token) => {
  if (!token) return { ok: true };
  const tokenHash = hashToken(token);
  await pool.query('DELETE FROM app_sessions WHERE token_hash = $1', [tokenHash]);
  return { ok: true };
};

const cleanupExpiredSessions = async (pool) => {
  await pool.query('DELETE FROM app_sessions WHERE expires_at <= NOW()');
};

module.exports = {
  ensureAuthTables,
  ensureDefaultAdmin,
  login,
  authenticateToken,
  logout,
  cleanupExpiredSessions
};
