<?php
// login.php
// Versión corregida: evita pantalla blanca por CSRF inválido y mejora espera visual del login.
declare(strict_types=1);

require_once __DIR__ . '/includes/auth.php';
require_once __DIR__ . '/includes/helpers.php';
require_once __DIR__ . '/includes/csrf.php';

ensure_session();

// Evita que el navegador muestre un formulario viejo con token CSRF vencido.
if (!headers_sent()) {
  header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
  header('Cache-Control: post-check=0, pre-check=0', false);
  header('Pragma: no-cache');
  header('Expires: 0');
}

const LOGIN_MAX_ATTEMPTS = 3;
const LOGIN_LOCK_SECONDS = 300; // 5 minutos de bloqueo temporal después de 3 intentos fallidos.
const LOGIN_CLIENT_TIMEOUT_MS = 18000; // Si tarda más, el botón vuelve a permitir intentar.

/**
 * CSRF tolerante para login.
 * ------------------------------------------------------------
 * Importante: NO usamos csrf_verify() directamente porque en algunas versiones
 * del proyecto hace die('CSRF inválido'), provocando pantalla blanca.
 * Aquí validamos y mostramos el error dentro del login.
 */
function login_csrf_token_safe(): string {
  try {
    if (function_exists('csrf_token')) {
      $token = (string)csrf_token();
      if ($token !== '') return $token;
    }
  } catch (Throwable $e) {}

  if (empty($_SESSION['csrf_token']) || !is_string($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
  }
  return (string)$_SESSION['csrf_token'];
}

function login_csrf_is_valid(string $postedToken): bool {
  $postedToken = trim($postedToken);
  if ($postedToken === '') return false;

  $candidates = [];

  // Token devuelto por la librería actual del proyecto.
  try {
    if (function_exists('csrf_token')) {
      $token = (string)csrf_token();
      if ($token !== '') $candidates[] = $token;
    }
  } catch (Throwable $e) {}

  // Compatibilidad con nombres comunes de token en sesión.
  foreach (['csrf', 'csrf_token', '_csrf', '_csrf_token', 'CSRF_TOKEN', 'breport_csrf', 'breport_csrf_token'] as $key) {
    if (!empty($_SESSION[$key]) && is_string($_SESSION[$key])) {
      $candidates[] = (string)$_SESSION[$key];
    }
  }

  $candidates = array_values(array_unique(array_filter($candidates, static fn($v) => is_string($v) && $v !== '')));
  foreach ($candidates as $validToken) {
    if (hash_equals($validToken, $postedToken)) return true;
  }
  return false;
}

function login_refresh_csrf_after_invalid(): string {
  // Genera un nuevo token compatible con fallback propio sin romper csrf.php.
  $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
  try {
    if (function_exists('csrf_token')) {
      $token = (string)csrf_token();
      if ($token !== '') return $token;
    }
  } catch (Throwable $e) {}
  return (string)$_SESSION['csrf_token'];
}

/**
 * Redirección inteligente posterior al login.
 * ------------------------------------------------------------
 * Reparto y Montacargas trabajan amarrados a Planta Botella.
 * Si el usuario pertenece a esos roles y tiene acceso válido a Botella,
 * se fija el scope automáticamente y se envía directo al módulo operativo.
 * Cualquier otro rol conserva el flujo normal hacia select_scope.php.
 */
function login_table_exists(PDO $pdo, string $table): bool {
  try {
    $st = $pdo->prepare("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?");
    $st->execute([$table]);
    return (int)$st->fetchColumn() > 0;
  } catch (Throwable $e) {
    return false;
  }
}

function login_current_user_id(PDO $pdo, string $username): int {
  try {
    if (function_exists('current_user')) {
      $cu = current_user();
      if (is_array($cu) && (int)($cu['id'] ?? 0) > 0) return (int)$cu['id'];
    }
  } catch (Throwable $e) {}

  $st = $pdo->prepare("SELECT id FROM users WHERE username = ? AND active = 1 LIMIT 1");
  $st->execute([$username]);
  return (int)($st->fetchColumn() ?: 0);
}

function login_user_roles(PDO $pdo, int $userId): array {
  if ($userId <= 0) return [];
  $st = $pdo->prepare("\n    SELECT LOWER(r.code) AS code\n    FROM user_roles ur\n    INNER JOIN roles r ON r.id = ur.role_id\n    WHERE ur.user_id = ?\n  ");
  $st->execute([$userId]);
  $roles = [];
  foreach ($st->fetchAll(PDO::FETCH_COLUMN) as $code) {
    $code = strtolower(trim((string)$code));
    if ($code !== '') $roles[$code] = true;
  }
  return array_keys($roles);
}

function login_has_any_role(array $roles, array $wanted): bool {
  $map = array_flip(array_map('strtolower', $roles));
  foreach ($wanted as $r) {
    if (isset($map[strtolower($r)])) return true;
  }
  return false;
}

function login_set_scope_safe(int $companyId, int $locationId): void {
  if (function_exists('set_scope')) {
    set_scope($companyId, $locationId);
    return;
  }
  $_SESSION['company_id'] = $companyId;
  $_SESSION['location_id'] = $locationId;
  $_SESSION['scope'] = [
    'company_id' => $companyId,
    'location_id' => $locationId,
  ];
  $_SESSION['breport_scope'] = [
    'company_id' => $companyId,
    'location_id' => $locationId,
  ];
}

function login_botella_scope_from_user_access(PDO $pdo, int $userId): ?array {
  $st = $pdo->prepare("\n    SELECT c.id AS company_id, l.id AS location_id, c.name AS company_name, l.name AS location_name\n    FROM companies c\n    INNER JOIN locations l ON l.company_id = c.id AND l.active = 1\n    INNER JOIN user_company_access uca ON uca.company_id = c.id AND uca.user_id = ?\n    INNER JOIN user_location_access ula ON ula.location_id = l.id AND ula.user_id = ?\n    WHERE c.active = 1\n      AND (UPPER(c.code) = 'BOTELLA' OR UPPER(c.name) LIKE '%BOTELLA%')\n    ORDER BY c.id, l.id\n    LIMIT 1\n  ");
  $st->execute([$userId, $userId]);
  $row = $st->fetch(PDO::FETCH_ASSOC);
  return $row ?: null;
}

function login_botella_scope_for_reparto(PDO $pdo, int $userId): ?array {
  if (login_table_exists($pdo, 'botella_route_user_assignments')) {
    try {
      $st = $pdo->prepare("\n        SELECT a.company_id, a.location_id, c.name AS company_name, l.name AS location_name,\n               a.route_no, a.warehouse_id\n        FROM botella_route_user_assignments a\n        INNER JOIN companies c ON c.id = a.company_id AND c.active = 1\n        INNER JOIN locations l ON l.id = a.location_id AND l.company_id = c.id AND l.active = 1\n        WHERE a.user_id = ?\n          AND a.active = 1\n          AND (UPPER(c.code) = 'BOTELLA' OR UPPER(c.name) LIKE '%BOTELLA%')\n        ORDER BY a.id DESC\n        LIMIT 1\n      ");
      $st->execute([$userId]);
      $row = $st->fetch(PDO::FETCH_ASSOC);
      if ($row) return $row;
    } catch (Throwable $e) {}
  }
  return login_botella_scope_from_user_access($pdo, $userId);
}

function login_botella_scope_for_montacargas(PDO $pdo, int $userId): ?array {
  return login_botella_scope_from_user_access($pdo, $userId);
}

function login_redirect_after_success(string $username): ?string {
  try {
    $pdo = pdo();
    $userId = login_current_user_id($pdo, $username);
    $roles = login_user_roles($pdo, $userId);

    // Roles de administración conservan el flujo normal de selección de planta.
    $isAdminLike = login_has_any_role($roles, ['admin', 'corp', 'plant_admin']);
    if ($isAdminLike) return 'select_scope.php';

    if (login_has_any_role($roles, ['reparto', 'repartidor'])) {
      $scope = login_botella_scope_for_reparto($pdo, $userId);
      if (!$scope) throw new RuntimeException('Tu usuario tiene rol Repartidor, pero no tiene planta/localidad Botella válida o ruta asignada.');
      login_set_scope_safe((int)$scope['company_id'], (int)$scope['location_id']);
      return 'reparto_botella.php';
    }

    if (login_has_any_role($roles, ['montacargas', 'montacarguista'])) {
      $scope = login_botella_scope_for_montacargas($pdo, $userId);
      if (!$scope) throw new RuntimeException('Tu usuario tiene rol Montacargas, pero no tiene planta/localidad Botella válida.');
      login_set_scope_safe((int)$scope['company_id'], (int)$scope['location_id']);
      return 'montacargas_botella.php';
    }

    return 'select_scope.php';
  } catch (Throwable $e) {
    // Se conserva el error para mostrarlo en pantalla sin dejar sesión en un scope incorrecto.
    $GLOBALS['err'] = $e->getMessage();
    return null;
  }
}

function login_lock_remaining_seconds(): int {
  $until = (int)($_SESSION['login_lock_until'] ?? 0);
  if ($until <= time()) {
    unset($_SESSION['login_lock_until']);
    return 0;
  }
  return $until - time();
}

function login_failed_attempts(): int {
  return max(0, (int)($_SESSION['login_failed_attempts'] ?? 0));
}

function login_attempts_left(): int {
  return max(0, LOGIN_MAX_ATTEMPTS - login_failed_attempts());
}

function login_register_failed_attempt(): int {
  $attempts = login_failed_attempts() + 1;
  $_SESSION['login_failed_attempts'] = $attempts;

  if ($attempts >= LOGIN_MAX_ATTEMPTS) {
    $_SESSION['login_lock_until'] = time() + LOGIN_LOCK_SECONDS;
    return 0;
  }

  return max(0, LOGIN_MAX_ATTEMPTS - $attempts);
}

function login_reset_failed_attempts(): void {
  unset($_SESSION['login_failed_attempts'], $_SESSION['login_lock_until']);
}

$err = '';
$attemptsLeft = login_attempts_left();
$lockRemaining = login_lock_remaining_seconds();
$csrfToken = login_csrf_token_safe();

if ($lockRemaining > 0) {
  $mins = max(1, (int)ceil($lockRemaining / 60));
  $err = 'Demasiados intentos fallidos. Intenta de nuevo en aproximadamente ' . $mins . ' minuto(s).';
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $postedCsrf = (string)($_POST['csrf'] ?? '');

  if (!login_csrf_is_valid($postedCsrf)) {
    // No cuenta como intento fallido porque es sesión vencida/formulario viejo.
    $err = 'La sesión expiró o el formulario no es válido. Intenta iniciar sesión nuevamente.';
    $csrfToken = login_refresh_csrf_after_invalid();
  } else {
    $u = trim((string)($_POST['username'] ?? ''));
    $p = (string)($_POST['password'] ?? '');
    $lockRemaining = login_lock_remaining_seconds();

    if ($lockRemaining > 0) {
      $mins = max(1, (int)ceil($lockRemaining / 60));
      $err = 'Acceso bloqueado temporalmente. Intenta de nuevo en aproximadamente ' . $mins . ' minuto(s).';
    } elseif ($u === '' || $p === '') {
      $err = 'Ingresa usuario y contraseña.';
    } elseif (!login($u, $p)) {
      $remaining = login_register_failed_attempt();
      $attemptsLeft = $remaining;
      if ($remaining <= 0) {
        $err = 'Inicio de sesión incorrecto. Se agotaron los 3 intentos. Intenta nuevamente en 5 minutos.';
      } else {
        $err = 'Inicio de sesión incorrecto. Te quedan ' . $remaining . ' intento(s).';
      }
    } else {
      login_reset_failed_attempts();

      // Mitiga fijación de sesión después de login exitoso.
      try { session_regenerate_id(true); } catch (Throwable $e) {}

      $target = login_redirect_after_success($u);
      if ($target !== null) {
        redirect($target);
      }
    }
  }

  $attemptsLeft = login_attempts_left();
  $lockRemaining = login_lock_remaining_seconds();
}

$csrfToken = $csrfToken !== '' ? $csrfToken : login_csrf_token_safe();
?>
<!doctype html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>BReport · Login</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">

  <style>
    :root{
      --glass-bg: rgba(10, 22, 35, 0.58);
      --glass-border: rgba(255,255,255,0.16);
      --text-soft: rgba(255,255,255,0.78);
      --input-bg: rgba(255,255,255,0.14);
      --input-border: rgba(255,255,255,0.18);
      --input-focus: rgba(255,255,255,0.28);
      --shadow-lg: 0 20px 45px rgba(0,0,0,.35);
    }

    html, body {
      height: 100%;
      margin: 0;
    }

    body {
      min-height: 100vh;
      position: relative;
      font-family: Arial, Helvetica, sans-serif;
      background:
        linear-gradient(rgba(4, 14, 24, 0.58), rgba(4, 14, 24, 0.72)),
        url('assets/login-bg.png') center center / cover no-repeat fixed;
      overflow-x: hidden;
    }

    body::before {
      content: "";
      position: fixed;
      inset: 0;
      background:
        radial-gradient(circle at top right, rgba(86, 194, 255, 0.16), transparent 30%),
        radial-gradient(circle at bottom left, rgba(255, 255, 255, 0.08), transparent 28%);
      pointer-events: none;
      z-index: 0;
    }

    .login-wrap {
      min-height: 100vh;
      display: flex;
      align-items: center;
      position: relative;
      z-index: 1;
      padding: 24px 12px;
    }

    .login-card {
      background: var(--glass-bg);
      border: 1px solid var(--glass-border);
      backdrop-filter: blur(12px);
      -webkit-backdrop-filter: blur(12px);
      border-radius: 24px;
      box-shadow: var(--shadow-lg);
      color: #fff;
      overflow: hidden;
    }

    .brand {
      display: flex;
      align-items: center;
      gap: 14px;
      margin-bottom: 1.25rem;
    }

    .brand img {
      width: 54px;
      height: 54px;
      object-fit: contain;
      border-radius: 12px;
      background: rgba(255,255,255,.10);
      padding: 6px;
    }

    .brand-title {
      font-size: 1.7rem;
      font-weight: 700;
      line-height: 1.1;
      margin: 0;
      letter-spacing: .3px;
    }

    .brand-subtitle {
      color: var(--text-soft);
      font-size: .93rem;
      margin: 0;
    }

    .form-label {
      color: #fff;
      font-weight: 600;
      margin-bottom: .45rem;
    }

    .form-control {
      background: var(--input-bg);
      border: 1px solid var(--input-border);
      color: #fff;
      min-height: 48px;
      border-radius: 14px;
      padding-left: 14px;
      padding-right: 14px;
    }

    .form-control::placeholder { color: rgba(255,255,255,.58); }

    .form-control:focus {
      background: rgba(255,255,255,.16);
      color: #fff;
      border-color: var(--input-focus);
      box-shadow: 0 0 0 .2rem rgba(255,255,255,.08);
    }

    .btn-login {
      min-height: 50px;
      border-radius: 14px;
      font-weight: 700;
      font-size: 1rem;
      letter-spacing: .2px;
      border: 0;
      transition: transform .15s ease, box-shadow .15s ease;
    }

    .btn-login:hover {
      transform: translateY(-1px);
      box-shadow: 0 8px 18px rgba(255,255,255,.12);
    }

    .alert { border-radius: 14px; }

    .footer-note {
      color: rgba(255,255,255,.78);
      text-shadow: 0 2px 10px rgba(0,0,0,.35);
    }

    .login-side-text {
      color: #fff;
      text-shadow: 0 4px 18px rgba(0,0,0,.45);
      margin-bottom: 18px;
    }

    .login-side-text h1 {
      font-size: clamp(1.8rem, 2.7vw, 3rem);
      font-weight: 800;
      margin-bottom: .5rem;
    }

    .login-side-text p {
      color: rgba(255,255,255,.88);
      max-width: 580px;
      font-size: clamp(.95rem, 1.2vw, 1.08rem);
      margin-bottom: 0;
    }

    .login-status {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 10px;
      margin-bottom: 1rem;
      padding: .72rem .85rem;
      border: 1px solid rgba(255,255,255,.14);
      background: rgba(255,255,255,.09);
      border-radius: 16px;
      color: rgba(255,255,255,.82);
      font-size: .86rem;
    }

    .attempt-badge {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      min-width: 34px;
      height: 28px;
      padding: 0 .65rem;
      border-radius: 999px;
      background: rgba(255,255,255,.18);
      color: #fff;
      font-weight: 800;
      white-space: nowrap;
    }

    .field-error {
      display: none;
      margin-top: .4rem;
      color: #ffd7d7;
      font-size: .82rem;
      font-weight: 600;
    }

    .form-control.is-invalid-local {
      border-color: rgba(255, 99, 132, .85);
      box-shadow: 0 0 0 .2rem rgba(255, 99, 132, .12);
    }

    .btn-login .spinner {
      display: none;
      width: 1.05rem;
      height: 1.05rem;
      border: 2px solid rgba(10,22,35,.25);
      border-top-color: rgba(10,22,35,.92);
      border-radius: 50%;
      animation: spin .75s linear infinite;
      margin-right: .55rem;
      vertical-align: -2px;
    }

    .btn-login.loading .spinner { display: inline-block; }
    .btn-login.loading { pointer-events: none; opacity: .92; }

    .loading-overlay {
      position: fixed;
      inset: 0;
      display: none;
      place-items: center;
      background: rgba(2, 8, 15, .58);
      backdrop-filter: blur(6px);
      -webkit-backdrop-filter: blur(6px);
      z-index: 50;
    }

    .loading-overlay.show { display: grid; }

    .loading-box {
      width: min(340px, calc(100vw - 36px));
      padding: 24px 22px;
      border-radius: 24px;
      border: 1px solid rgba(255,255,255,.18);
      background: rgba(10,22,35,.78);
      box-shadow: var(--shadow-lg);
      color: #fff;
      text-align: center;
    }

    .loading-circle {
      width: 54px;
      height: 54px;
      border: 4px solid rgba(255,255,255,.24);
      border-top-color: #fff;
      border-radius: 50%;
      animation: spin .8s linear infinite;
      margin: 0 auto 14px;
    }

    @keyframes spin { to { transform: rotate(360deg); } }

    @media (max-width: 767.98px) {
      body {
        background:
          linear-gradient(rgba(4, 14, 24, 0.66), rgba(4, 14, 24, 0.80)),
          url('assets/login-bg.png') center center / cover no-repeat;
      }

      .login-card { border-radius: 20px; }
      .brand-title { font-size: 1.45rem; }
      .brand img { width: 48px; height: 48px; }
      .login-side-text { text-align: center; margin-bottom: 24px; }
      .login-side-text p { margin-left: auto; margin-right: auto; }
    }
  </style>
</head>
<body>
  <div class="container-fluid login-wrap">
    <div class="container">
      <div class="row align-items-center justify-content-center g-4">

        <div class="col-12 col-lg-6 d-none d-lg-block">
          <div class="login-side-text">
            <h1>BReport</h1>
            <p>Sistema de reportaje de producción (MES) Blanquita</p>
          </div>
        </div>

        <div class="col-12 col-sm-11 col-md-8 col-lg-5 col-xl-4">
          <div class="card login-card">
            <div class="card-body p-4 p-md-5">

              <div class="brand">
                <img src="assets/logo.png" alt="BReport">
                <div>
                  <div class="brand-title">BReport</div>
                  <p class="brand-subtitle">Acceso seguro</p>
                </div>
              </div>

              <?php if ($err !== ''): ?>
                <div class="alert alert-danger py-2 px-3 mb-3" id="serverAlert">
                  <?= h($err) ?>
                </div>
              <?php endif; ?>

              <div class="alert alert-warning py-2 px-3 mb-3 d-none" id="clientTimeoutAlert">
                La validación tardó más de lo esperado. Revisa tu conexión e intenta nuevamente.
              </div>

              <div class="login-status">
                <span>Validación segura de acceso</span>
                <span class="attempt-badge">Intentos: <?= h((string)$attemptsLeft) ?>/<?= LOGIN_MAX_ATTEMPTS ?></span>
              </div>

              <form id="loginForm" method="post" autocomplete="off" novalidate>
                <input type="hidden" name="csrf" value="<?= h($csrfToken) ?>">

                <div class="mb-3">
                  <label class="form-label" for="username">Usuario</label>
                  <input
                    id="username"
                    class="form-control"
                    name="username"
                    required
                    value="<?= h((string)($_POST['username'] ?? '')) ?>"
                    placeholder="Ingresa tu usuario">
                  <div class="field-error" id="usernameError">Captura tu usuario.</div>
                </div>

                <div class="mb-3">
                  <label class="form-label" for="password">Contraseña</label>
                  <input
                    id="password"
                    class="form-control"
                    type="password"
                    name="password"
                    required
                    placeholder="Ingresa tu contraseña">
                  <div class="field-error" id="passwordError">Captura tu contraseña.</div>
                </div>

                <button id="loginButton" class="btn btn-light btn-login w-100" type="submit" <?= $lockRemaining > 0 ? 'disabled' : '' ?>>
                  <span class="spinner" aria-hidden="true"></span>
                  <span class="btn-text">Entrar</span>
                </button>
              </form>

              <div class="mt-3 text-white-50 small"></div>
            </div>
          </div>

          <div class="text-center footer-note small mt-3">
            © <?= date('Y') ?> Productos blanquita
          </div>
        </div>

      </div>
    </div>
  </div>

  <div class="loading-overlay" id="loadingOverlay" aria-hidden="true">
    <div class="loading-box">
      <div class="loading-circle"></div>
      <div class="fw-bold mb-1">Validando acceso</div>
      <div class="small text-white-50">Estamos revisando usuario, rol y planta asignada...</div>
      <div class="small text-white-50 mt-2">No cierres esta ventana.</div>
    </div>
  </div>

  <script>
    const LOGIN_CLIENT_TIMEOUT_MS = <?= (int)LOGIN_CLIENT_TIMEOUT_MS ?>;
    const form = document.getElementById('loginForm');
    const btn = document.getElementById('loginButton');
    const overlay = document.getElementById('loadingOverlay');
    const username = document.getElementById('username');
    const password = document.getElementById('password');
    const clientTimeoutAlert = document.getElementById('clientTimeoutAlert');
    let loginTimer = null;

    function setInvalid(input, errorId, invalid) {
      const error = document.getElementById(errorId);
      input.classList.toggle('is-invalid-local', invalid);
      if (error) error.style.display = invalid ? 'block' : 'none';
    }

    function validateLocal() {
      const userBad = username.value.trim() === '';
      const passBad = password.value === '';
      setInvalid(username, 'usernameError', userBad);
      setInvalid(password, 'passwordError', passBad);
      return !userBad && !passBad;
    }

    function setLoading(isLoading) {
      if (!btn) return;
      btn.classList.toggle('loading', isLoading);
      btn.disabled = isLoading;
      const txt = btn.querySelector('.btn-text');
      if (txt) txt.textContent = isLoading ? 'Validando...' : 'Entrar';
      overlay?.classList.toggle('show', isLoading);
      overlay?.setAttribute('aria-hidden', isLoading ? 'false' : 'true');
    }

    [username, password].forEach((el) => {
      el.addEventListener('input', () => {
        if (el === username) setInvalid(username, 'usernameError', username.value.trim() === '');
        if (el === password) setInvalid(password, 'passwordError', password.value === '');
        clientTimeoutAlert?.classList.add('d-none');
      });
    });

    form?.addEventListener('submit', (ev) => {
      if (!validateLocal()) {
        ev.preventDefault();
        return;
      }

      setLoading(true);
      clientTimeoutAlert?.classList.add('d-none');

      // Si por red lenta o error del servidor no cambia de página, el login no queda atorado visualmente.
      clearTimeout(loginTimer);
      loginTimer = setTimeout(() => {
        setLoading(false);
        clientTimeoutAlert?.classList.remove('d-none');
      }, LOGIN_CLIENT_TIMEOUT_MS);
    });

    window.addEventListener('pageshow', () => {
      // Si el navegador regresa desde cache, limpiamos estado visual.
      setLoading(false);
      clearTimeout(loginTimer);
    });
  </script>
</body>
</html>
