/**
 * src/routes/auth.js — маршруты аутентификации.
 *
 * OIDC-режим (KC_CLIENT_ID + KC_CLIENT_SECRET заданы в окружении):
 *   GET  /login      → генерирует state, сохраняет в сессии, редиректит на Keycloak
 *   GET  /callback   → проверяет state, обменивает code→токен, кладёт user в сессию
 *   GET  /logout     → уничтожает сессию + редирект на KK logout
 *
 * Mock-режим (нет OIDC credentials):
 *   GET  /login      → рендерит views/login.ejs — список MOCK_USERS
 *   POST /login      → CSRF → сохраняет выбранного user в сессию → /
 *   GET  /logout     → уничтожает сессию → /login
 *
 * Dev-backdoor (DEV_MODE=true или DEV_SECRET задан):
 *   GET  /dev-login  → рендерит views/dev-login.ejs (требует ?key если DEV_SECRET задан)
 *   POST /dev-login  → CSRF → кладёт произвольного user в сессию → /
 *   Работает в ЛЮБОМ режиме (OIDC или mock) — обходит нормальный поток.
 *
 * @param {{ auth, doubleCsrfProtection, generateCsrfToken, MOCK_USERS }} deps
 * @returns {import('express').Router}
 */

'use strict';

const crypto     = require('crypto');
const { Router } = require('express');

/**
 * Валидирует redirect-цель: разрешает только локальные пути (начинается с '/',
 * не начинается с '//').
 * Защита от open redirect: //evil.com и https://evil.com → '/'.
 * @param {string|undefined} target
 * @returns {string}
 */
function safeReturn(target) {
  if (typeof target === 'string' && target.startsWith('/') && !target.startsWith('//')) {
    return target;
  }
  return '/';
}

function createRouter({ auth, doubleCsrfProtection, generateCsrfToken, MOCK_USERS, authLimiter }) {
  const router = Router();

  // ── GET /login ─────────────────────────────────────────────────────────────
  router.get('/login', (req, res) => {
    if (auth.isOidc) {
      // OIDC: генерируем state (защита от CSRF на callback), редиректим на KK
      const state = crypto.randomBytes(16).toString('hex');
      req.session.oidcState = state;
      req.session.returnTo  = req.query.returnTo || '/';
      return res.redirect(auth.buildAuthUrl(state));
    }

    // Mock-режим: форма выбора пользователя
    const csrfToken = generateCsrfToken(req, res);
    res.render('login', {
      users:       MOCK_USERS,
      error:       req.query.error || null,
      csrfToken,
      devLoginUrl: auth.devLoginEnabled ? '/dev-login' : null,
    });
  });

  // ── POST /login (только mock-режим) ────────────────────────────────────────
  router.post('/login', authLimiter, doubleCsrfProtection, (req, res) => {
    if (auth.isOidc) return res.redirect('/login');

    const user = MOCK_USERS.find(u => u.id === req.body.user);
    if (!user) {
      return res.redirect('/login?error=' + encodeURIComponent('Пользователь не найден'));
    }

    const userData = {
      email:       user.email,
      clientId:    user.clientId,
      companyId:   user.companyId,
      companyName: user.companyName,
      isAdmin:     user.role === 'admin',
    };

    // session.regenerate() меняет идентификатор сессии — защита от session fixation (A07).
    // Данные пишем уже в новую сессию.
    req.session.regenerate((err) => {
      if (err) { console.error('[login] session.regenerate error:', err); return res.status(500).send('Session error'); }
      req.session.user = userData;
      req.session.save((saveErr) => {
        if (saveErr) { console.error('[login] session.save error:', saveErr); return res.status(500).send('Session error'); }
        res.redirect(safeReturn(req.query.returnTo));
      });
    });
  });

  // ── GET /callback (только OIDC-режим) ──────────────────────────────────────
  router.get('/callback', authLimiter, async (req, res) => {
    if (!auth.isOidc) return res.redirect('/login');

    const { code, state, error } = req.query;

    if (error) {
      console.error('[auth] Keycloak error:', error, req.query.error_description || '');
      return res.redirect('/login?error=' + encodeURIComponent('Keycloak: ' + error));
    }

    // Проверяем state (защита от CSRF)
    if (!state || state !== req.session.oidcState) {
      return res.redirect('/login?error=' + encodeURIComponent('Недействительный state'));
    }
    delete req.session.oidcState;

    try {
      const { user, idToken, refreshToken } = await auth.exchangeCode(code);
      // Сохраняем returnTo до regenerate, так как regenerate сбросит сессию.
      const returnTo = safeReturn(req.session.returnTo);
      // session.regenerate() — защита от session fixation (A07).
      req.session.regenerate((err) => {
        if (err) { console.error('[callback] session.regenerate error:', err); return res.status(500).send('Session error'); }
        req.session.user         = user;
        req.session.idToken      = idToken;
        req.session.refreshToken = refreshToken;
        req.session.save((saveErr) => {
          if (saveErr) { console.error('[callback] session.save error:', saveErr); return res.status(500).send('Session error'); }
          res.redirect(returnTo);
        });
      });
    } catch (e) {
      console.error('[auth] exchangeCode error:', e.message);
      res.redirect('/login?error=' + encodeURIComponent('Ошибка авторизации: ' + e.message));
    }
  });

  // ── GET /dev-login ─────────────────────────────────────────────────────────
  //
  // Тестовый вход в обход OIDC/mock. Доступен если:
  //   • DEV_MODE=true  → без пароля
  //   • DEV_SECRET=xyz → с ?key=xyz или session.devKey
  router.get('/dev-login', (req, res) => {
    if (!auth.devLoginEnabled) return res.status(404).send('Not Found');

    if (auth.DEV_SECRET) {
      const keyOk = req.query.key === auth.DEV_SECRET || req.session.devKey === auth.DEV_SECRET;
      if (!keyOk) return res.status(403).send('403 Forbidden: неверный dev-ключ');
      req.session.devKey = auth.DEV_SECRET; // кешируем в сессии: ?key= нужен только раз
    }

    const csrfToken = generateCsrfToken(req, res);
    res.render('dev-login', {
      users:     MOCK_USERS,
      error:     req.query.error || null,
      csrfToken,
    });
  });

  // ── POST /dev-login ────────────────────────────────────────────────────────
  router.post('/dev-login', authLimiter, doubleCsrfProtection, (req, res) => {
    if (!auth.devLoginEnabled) return res.status(404).send('Not Found');

    if (auth.DEV_SECRET && req.session.devKey !== auth.DEV_SECRET) {
      return res.status(403).send('403 Forbidden');
    }

    let user;

    if (req.body.preset) {
      // Пресет из MOCK_USERS
      const mockUser = MOCK_USERS.find(u => u.id === req.body.preset);
      if (!mockUser) {
        return res.redirect('/dev-login?error=' + encodeURIComponent('Пользователь не найден'));
      }
      user = {
        email:       mockUser.email,
        clientId:    mockUser.clientId,
        companyId:   mockUser.companyId,
        companyName: mockUser.companyName,
        isAdmin:     mockUser.role === 'admin',
      };
    } else {
      // Произвольные поля
      const clientId = (req.body.clientId || '').trim();
      if (!clientId) {
        return res.redirect('/dev-login?error=' + encodeURIComponent('ClientID обязателен'));
      }
      user = {
        email:       (req.body.email       || '').trim() || clientId + '@dev.local',
        clientId,
        companyId:   (req.body.companyId   || '').trim() || null,
        companyName: (req.body.companyName || '').trim() || clientId,
        isAdmin:     req.body.isAdmin === 'on',
      };
    }

    req.session.regenerate((err) => {
      if (err) { console.error('[dev-login] session.regenerate error:', err); return res.status(500).send('Session error'); }
      req.session.user = user;
      // Восстанавливаем devKey в новой сессии — иначе следующий запрос потребует повторного ?key=
      if (auth.DEV_SECRET) req.session.devKey = auth.DEV_SECRET;
      req.session.save((saveErr) => {
        if (saveErr) { console.error('[dev-login] session.save error:', saveErr); return res.status(500).send('Session error'); }
        res.redirect('/');
      });
    });
  });

  // ── GET /logout ────────────────────────────────────────────────────────────
  router.get('/logout', (req, res) => {
    const idToken = req.session.idToken || null;
    const isOidc  = auth.isOidc;

    req.session.destroy(() => {
      res.clearCookie('connect.sid');
      res.clearCookie('jwt'); // удаляем устаревший cookie если остался от старой версии

      if (isOidc && idToken) {
        return res.redirect(auth.buildLogoutUrl(idToken));
      }
      res.redirect('/login');
    });
  });

  return router;
}

module.exports = { createRouter, safeReturn };
