'use strict';

const express = require('express');
const router  = express.Router();
const bcrypt  = require('bcryptjs');
const { passwordLimiter } = require('../middleware/rateLimiter');

const { getDb, getSetting } = require('../db/database');
const { hashPassword, invalidateOtherSessions, rotateSessionId, COOKIE_NAME, cookieOpts } = require('../services/authService');
const { getImportHistory }  = require('../services/spreadsheetImportService');
const { logAudit }          = require('../services/auditService');
const { standardizeError }  = require('../middleware/errorFormatter');
const { encryptSecret, decryptSecret } = require('../services/encryptionService');

// All profile routes require authentication — enforced in server.js.
// req.user is always the signed-in user; user_id is never accepted from the body.

function dataImportEnabled() {
  return String(process.env.DATA_IMPORT_ENABLED ?? 'true').toLowerCase() !== 'false';
}

function requireDataImportEnabled(req, res, next) {
  if (!dataImportEnabled()) {
    return res.status(403).json(standardizeError('Data import is disabled by DATA_IMPORT_ENABLED=false', 'FORBIDDEN'));
  }
  next();
}

function profileResponse(user) {
  const displayName = user.display_name || null;
  return {
    id:                      user.id,
    username:                user.username,
    display_name:            displayName,
    displayName,
    name:                    displayName || user.username,
    role:                    user.role,
    active:                  !!user.active,
    is_default_admin:        !!user.is_default_admin,
    created_at:              user.created_at,
    updated_at:              user.updated_at,
    last_password_change_at: user.last_password_change_at || null,
    first_login:             !!user.first_login,
  };
}

// ── GET /api/profile ──────────────────────────────────────────────────────────
// Returns safe profile data for the signed-in user.
// Never returns password_hash, session tokens, or secrets.
router.get('/', (req, res) => {
  const db   = getDb();
  const user = db.prepare(`
    SELECT id, username, display_name, role, active, is_default_admin,
           first_login, created_at, updated_at,
           last_password_change_at,
           notification_email, notifications_enabled,
           notify_3d, notify_1d, notify_due, notify_overdue
    FROM users WHERE id = ?
  `).get(req.user.id);

  if (!user) return res.status(404).json({ error: 'User not found' });

  res.json({
    ...profileResponse(user),
    notifications: {
      email:          user.notification_email  || null,
      enabled:        !!user.notifications_enabled,
      notify_3d:      !!user.notify_3d,
      notify_1d:      !!user.notify_1d,
      notify_due:     !!user.notify_due,
      notify_overdue: !!user.notify_overdue,
    },
    exports: {
      user_db:    '/api/export/user-db',
      user_excel: '/api/export/user-excel',
    },
    import_history_url: '/api/profile/import-history',
  });
});

// ── PATCH /api/profile ────────────────────────────────────────────────────────
// Updates safe profile fields: username and display_name.
// Ignores any unknown or restricted fields.
router.patch('/', (req, res) => {
  const { username } = req.body;
  const displayNameInput = req.body.display_name !== undefined
    ? req.body.display_name
    : req.body.displayName !== undefined
    ? req.body.displayName
    : req.body.name;
  const db = getDb();

  if (username !== undefined) {
    if (typeof username !== 'string') {
      return res.status(400).json({ error: 'username must be a string' });
    }
    const trimmedUsername = username.trim();
    if (trimmedUsername.length < 3) {
      return res.status(400).json({ error: 'username must be at least 3 characters' });
    }
    if (trimmedUsername.length > 50) {
      return res.status(400).json({ error: 'username must be 50 characters or fewer' });
    }
    const taken = db.prepare(
      'SELECT id FROM users WHERE username = ? COLLATE NOCASE AND id != ?'
    ).get(trimmedUsername, req.user.id);
    if (taken) {
      return res.status(409).json({ error: 'Username already taken' });
    }
    db.prepare(
      "UPDATE users SET username = ?, updated_at = datetime('now') WHERE id = ?"
    ).run(trimmedUsername, req.user.id);
    logAudit({ user_id: req.user.id, action: 'profile.username.change', ip_address: req.ip, user_agent: req.get('user-agent') });
  }

  if (displayNameInput !== undefined) {
    if (typeof displayNameInput !== 'string') {
      return res.status(400).json({ error: 'display_name must be a string' });
    }
    const trimmed = displayNameInput.trim();
    if (trimmed.length > 100) {
      return res.status(400).json({ error: 'display_name must be 100 characters or fewer' });
    }

    db.prepare(
      "UPDATE users SET display_name = ?, updated_at = datetime('now') WHERE id = ?"
    ).run(trimmed || null, req.user.id);

    logAudit({ user_id: req.user.id, action: 'profile.update', ip_address: req.ip, user_agent: req.get('user-agent') });
  }

  const updated = getDb().prepare(`
    SELECT id, username, display_name, role, active, is_default_admin,
           first_login, created_at, updated_at,
           last_password_change_at
    FROM users WHERE id = ?
  `).get(req.user.id);

  res.json({ success: true, profile: profileResponse(updated) });
});

// ── GET /api/profile/settings ─────────────────────────────────────────────────
// Returns user-owned notification preferences from the users table.
// Does not return admin/global/SMTP settings.
router.get('/settings', (req, res) => {
  const db   = getDb();
  const user = db.prepare(`
    SELECT notification_email, notifications_enabled,
           notify_3d, notify_1d, notify_due, notify_overdue, notify_amount_change,
           notify_push_enabled, push_channel, push_url, push_token, push_chat_id,
           geolocation_enabled
    FROM users WHERE id = ?
  `).get(req.user.id);

  if (!user) return res.status(404).json({ error: 'User not found' });

  // Decrypt push secrets — return masked indicator for token (never the raw value)
  const pushUrlDecrypted = user.push_url ? (() => { try { return decryptSecret(user.push_url); } catch { return user.push_url; } })() : null;

  res.json({
    notification_email:    user.notification_email  || null,
    notifications_enabled: !!user.notifications_enabled,
    notify_3d:             !!user.notify_3d,
    notify_1d:             !!user.notify_1d,
    notify_due:            !!user.notify_due,
    notify_overdue:        !!user.notify_overdue,
    notify_amount_change:  user.notify_amount_change !== 0,
    notify_push_enabled:   !!user.notify_push_enabled,
    push_channel:          user.push_channel || null,
    push_url:              pushUrlDecrypted || null,
    push_token_set:        !!user.push_token,
    push_chat_id:          user.push_chat_id || null,
    geolocation_enabled:   !!user.geolocation_enabled,
  });
});

// ── PATCH /api/profile/settings ───────────────────────────────────────────────
// Updates user-owned notification preferences only.
// Cannot modify global/admin/SMTP settings through this endpoint.
const VALID_PUSH_CHANNELS = new Set(['ntfy', 'gotify', 'discord', 'telegram']);

router.patch('/settings', (req, res) => {
  const db = getDb();
  const {
    notification_email, email, notifications_enabled,
    notify_3d, notify_1d, notify_due, notify_overdue, notify_amount_change,
    notify_push_enabled, push_channel, push_url, push_token, push_chat_id,
    geolocation_enabled,
  } = req.body;

  const nextEmail = notification_email !== undefined ? notification_email : email;

  if (nextEmail !== undefined && nextEmail !== null) {
    if (typeof nextEmail !== 'string') {
      return res.status(400).json({ error: 'notification_email must be a string' });
    }
    if (nextEmail.trim().length > 255) {
      return res.status(400).json({ error: 'notification_email is too long' });
    }
  }

  if (push_channel !== undefined && push_channel !== null && !VALID_PUSH_CHANNELS.has(push_channel)) {
    return res.status(400).json({ error: `push_channel must be one of: ${[...VALID_PUSH_CHANNELS].join(', ')}` });
  }

  const current = db.prepare(`
    SELECT notification_email, notifications_enabled,
           notify_3d, notify_1d, notify_due, notify_overdue, notify_amount_change,
           notify_push_enabled, push_channel, push_url, push_token, push_chat_id,
           geolocation_enabled
    FROM users WHERE id = ?
  `).get(req.user.id);

  const emailVal = nextEmail !== undefined
    ? (nextEmail ? nextEmail.trim() || null : null)
    : current.notification_email;

  const boolVal = (incoming, fallback) =>
    incoming !== undefined ? (incoming ? 1 : 0) : fallback;

  // Encrypt push_url and push_token before storing
  const encryptOrNull = (v, fallback) => {
    if (v === undefined) return fallback;
    if (!v) return null;
    try { return encryptSecret(String(v).trim()); } catch { return String(v).trim(); }
  };

  db.prepare(`
    UPDATE users SET
      notification_email    = ?,
      notifications_enabled = ?,
      notify_3d             = ?,
      notify_1d             = ?,
      notify_due            = ?,
      notify_overdue        = ?,
      notify_amount_change  = ?,
      notify_push_enabled   = ?,
      push_channel          = ?,
      push_url              = ?,
      push_token            = ?,
      push_chat_id          = ?,
      geolocation_enabled   = ?,
      updated_at            = datetime('now')
    WHERE id = ?
  `).run(
    emailVal,
    boolVal(notifications_enabled,  current.notifications_enabled),
    boolVal(notify_3d,              current.notify_3d),
    boolVal(notify_1d,              current.notify_1d),
    boolVal(notify_due,             current.notify_due),
    boolVal(notify_overdue,         current.notify_overdue),
    boolVal(notify_amount_change,   current.notify_amount_change),
    boolVal(notify_push_enabled,    current.notify_push_enabled),
    push_channel !== undefined ? (push_channel || null) : current.push_channel,
    encryptOrNull(push_url,   current.push_url),
    encryptOrNull(push_token, current.push_token),
    push_chat_id !== undefined ? (push_chat_id?.trim() || null) : current.push_chat_id,
    boolVal(geolocation_enabled, current.geolocation_enabled),
    req.user.id,
  );

  logAudit({ user_id: req.user.id, action: 'profile.settings.update', ip_address: req.ip, user_agent: req.get('user-agent') });

  res.json({ success: true });
});

// ── POST /api/profile/change-password ─────────────────────────────────────────
// Changes the signed-in user's password.
// Always requires: current_password, new_password, confirm_new_password.
// Never bypasses current_password verification regardless of must_change_password.
// Never accepts user_id from the request body.
router.post('/change-password', passwordLimiter, async (req, res) => {
  const { current_password, new_password, confirm_new_password } = req.body;

  if (!current_password) {
    return res.status(400).json({ error: 'current_password is required' });
  }
  if (!new_password) {
    return res.status(400).json({ error: 'new_password is required' });
  }
  if (!confirm_new_password) {
    return res.status(400).json({ error: 'confirm_new_password is required' });
  }
  if (new_password !== confirm_new_password) {
    return res.status(400).json({ error: 'new passwords do not match' });
  }
  if (new_password.length < 8) {
    return res.status(400).json({ error: 'new password must be at least 8 characters' });
  }

  const db   = getDb();
  const user = db.prepare('SELECT password_hash FROM users WHERE id = ?').get(req.user.id);
  if (!user) return res.status(404).json({ error: 'User not found' });

  try {
    const valid = await bcrypt.compare(current_password, user.password_hash);
    if (!valid) {
      return res.status(401).json({ error: 'current password is incorrect' });
    }

    const hash = await hashPassword(new_password);

    db.prepare(`
      UPDATE users
      SET password_hash = ?, must_change_password = 0,
          last_password_change_at = datetime('now'),
          updated_at = datetime('now')
      WHERE id = ?
    `).run(hash, req.user.id);

    // Invalidate all other sessions for this user
    const currentSessionId = req.cookies?.[COOKIE_NAME];
    if (currentSessionId) {
      invalidateOtherSessions(req.user.id, currentSessionId);

      // Rotate the current session ID for security
      const newSessionId = rotateSessionId(currentSessionId, req.user.id);
      if (newSessionId) {
        res.cookie(COOKIE_NAME, newSessionId, cookieOpts(req));
      }
    }

    logAudit({ user_id: req.user.id, action: 'password.change', ip_address: req.ip, user_agent: req.get('user-agent') });

    res.json({ success: true });
  } catch (err) {
    console.error('[profile] change-password error:', err.message);
    res.status(500).json({ error: 'Password change failed' });
  }
});

// ── GET /api/profile/exports ──────────────────────────────────────────────────
// Returns metadata about available user export capabilities.
// Does not trigger any download; links point to the actual export endpoints.
router.get('/exports', (req, res) => {
  res.json({
    exports: [
      {
        id:          'user_db',
        label:       'SQLite database export',
        description: "Your bills, categories, payments, and notes as a portable SQLite file",
        url:         '/api/export/user-db',
        method:      'GET',
      },
      {
        id:          'user_excel',
        label:       'Excel databook export',
        description: "Your data as an Excel workbook with multiple sheets",
        url:         '/api/export/user-excel',
        method:      'GET',
      },
    ],
  });
});

// ── GET /api/profile/import-history ──────────────────────────────────────────
// Returns the signed-in user's import history.
// Delegates to the same service as GET /api/import/history.
router.get('/import-history', requireDataImportEnabled, (req, res) => {
  try {
    const history = getImportHistory(req.user.id);
    res.json({ history });
  } catch (err) {
    console.error('[profile] import-history error:', err.message);
    res.status(500).json({ error: 'Failed to load import history' });
  }
});

module.exports = router;
