<?php
/**
 * auth.php — sessão, helpers, RBAC, política de senha e logs
 * Versão tolerante a migrações parciais (não quebra se tabela/coluna faltar)
 */

declare(strict_types=1);

/* ===================== CONFIG GERAL ===================== */
@date_default_timezone_set('America/Sao_Paulo');

/* ===================== DB ===================== */
$dsn  = 'mysql:host=localhost;dbname=emprestait;charset=utf8mb4';
$user = 'emprestait_user';      // <<< AJUSTE se for outro usuário
$pass = 'SENHA_FORTE_AQUI';  // <<< AJUSTE a senha correta

$options = [
  PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
  PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];
$pdo = new PDO($dsn, $user, $pass, $options);

/* ===================== SESSÃO ===================== */
if (session_status() !== PHP_SESSION_ACTIVE) {
  session_set_cookie_params(['httponly'=>true,'samesite'=>'Lax']);
  session_start();
}

/* ===================== HELPERS BÁSICOS ===================== */
function base_url(string $path=''): string {
  $script = $_SERVER['SCRIPT_NAME'] ?? '';
  $base = rtrim(dirname($script), '/\\');
  if ($base==='') $base='/';
  return $base.'/'.ltrim($path,'/');
}
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8'); }
function br_date(?string $d){ if(!$d) return '—'; $t=strtotime($d); return $t?date('d/m/Y',$t):'—'; }
function br_datetime(?string $d){ if(!$d) return '—'; $t=strtotime($d); return $t?date('d/m/Y H:i',$t):'—'; }

/* CSRF */
function csrf_token(): string {
  if(empty($_SESSION['csrf'])) $_SESSION['csrf']=bin2hex(random_bytes(16));
  return $_SESSION['csrf'];
}
function check_csrf(): void {
  if($_SERVER['REQUEST_METHOD']==='POST'){
    if(($_POST['csrf']??'')!==($_SESSION['csrf']??'')){
      http_response_code(403); exit('CSRF inválido');
    }
  }
}

/* ===================== POLÍTICAS / RBAC ===================== */
const PWD_MIN_LEN            = 10;
const PWD_REQUIRE_UPPER      = true;
const PWD_REQUIRE_LOWER      = true;
const PWD_REQUIRE_DIGIT      = true;
const PWD_REQUIRE_SYMBOL     = true;

const PWD_MAX_FAILS          = 5;
const LOCK_MINUTES           = 15;

const PASSWORD_EXPIRY_DAYS   = 90;

const ACCESS_LOG_RETENTION_DAYS = 180;

/* Permissões por papel */
$ROLE_PERMS = [
  'ADMIN'   => ['*'],
  'TI'      => ['dashboard.view','loans.*','equipment.*','returns.*','alerts.*','reports.*','employees.*','operations.*','types.*','locations.*','scan.*','history.*','users.*','logs.view'],
  'RH'      => ['rh.portal','returns.view','returns.create'],
  'AUDITOR' => ['dashboard.view','reports.*','history.*','returns.view','loans.view','equipment.view','logs.view'],
  'VIEWER'  => ['dashboard.view'],
];

/* Usuário atual */
function current_user(){ return $_SESSION['user'] ?? null; }
function is_logged(): bool { return !empty($_SESSION['user']); }

/* Checagem de permissão */
function user_has_perm(string $perm): bool {
  global $ROLE_PERMS; $u=current_user(); if(!$u) return false;
  $perms = $ROLE_PERMS[$u['role'] ?? 'VIEWER'] ?? [];
  if (in_array('*',$perms,true)) return true;
  if (in_array($perm,$perms,true)) return true;
  $root = strtok($perm,'.');
  return in_array($root.'.*',$perms,true);
}

/* ===================== UTIL DE SCHEMA ===================== */
function db_has_table(PDO $pdo, string $t): bool {
  $s=$pdo->prepare("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name=?");
  $s->execute([$t]); return (bool)$s->fetchColumn();
}
function db_has_col(PDO $pdo, string $t, string $c): bool {
  $s=$pdo->prepare("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name=? AND column_name=?");
  $s->execute([$t,$c]); return (bool)$s->fetchColumn();
}

/* ===================== LOG DE ACESSO (tolerante) ===================== */
function log_access(PDO $pdo, string $action, string $notes=''): void {
  try{
    if(!db_has_table($pdo,'access_log')) return;
    $uid = current_user()['id'] ?? null;
    $pdo->prepare("INSERT INTO access_log (user_id,action,ip,user_agent,path,notes) VALUES (?,?,?,?,?,?)")
        ->execute([
          $uid,
          $action,
          $_SERVER['REMOTE_ADDR'] ?? null,
          substr($_SERVER['HTTP_USER_AGENT'] ?? '',0,255),
          substr($_SERVER['REQUEST_URI'] ?? '',0,255),
          substr($notes,0,255)
        ]);
  }catch(Throwable $e){/* ignora */}
}
/* limpeza 1x/dia */
if (empty($_SESSION['last_log_cleanup']) || $_SESSION['last_log_cleanup'] < date('Y-m-d')) {
  $_SESSION['last_log_cleanup']=date('Y-m-d');
  try{
    if (db_has_table($pdo,'access_log')) {
      $pdo->prepare("DELETE FROM access_log WHERE created_at < DATE_SUB(NOW(), INTERVAL ? DAY)")
          ->execute([ACCESS_LOG_RETENTION_DAYS]);
    }
  }catch(Throwable $e){/* ignora */}
}

/* ===================== AUTENTICAÇÃO ===================== */
function do_login(PDO $pdo, string $username, string $password): array {
  // tenta por username; se coluna não existir, cai para name
  if (db_has_col($pdo,'users','username')) {
    $st=$pdo->prepare("SELECT * FROM users WHERE username=? LIMIT 1");
    $st->execute([$username]);
  } else {
    $st=$pdo->prepare("SELECT * FROM users WHERE name=? LIMIT 1");
    $st->execute([$username]);
  }
  $user=$st->fetch();
  if(!$user){ log_access($pdo,'LOGIN_FAIL','usuario'); return ['ok'=>false,'msg'=>'Usuário ou senha inválidos']; }

  if (isset($user['locked_until']) && $user['locked_until'] && strtotime($user['locked_until'])>time()){
    return ['ok'=>false,'msg'=>'Conta bloqueada temporariamente. Tente mais tarde.'];
  }
  if (isset($user['is_active']) && !$user['is_active']) {
    return ['ok'=>false,'msg'=>'Conta desativada. Procure o administrador.'];
  }

  if (!password_verify($password, $user['password_hash'] ?? '')) {
    try{
      if (db_has_col($pdo,'users','failed_logins') && db_has_col($pdo,'users','locked_until')) {
        $pdo->prepare("UPDATE users SET failed_logins=failed_logins+1, locked_until=IF(failed_logins+1>=?, DATE_ADD(NOW(), INTERVAL ? MINUTE), locked_until) WHERE id=?")
            ->execute([PWD_MAX_FAILS, LOCK_MINUTES, (int)$user['id']]);
      }
    }catch(Throwable $e){}
    log_access($pdo,'LOGIN_FAIL','senha');
    return ['ok'=>false,'msg'=>'Usuário ou senha inválidos'];
  }

  try{
    if (db_has_col($pdo,'users','failed_logins') && db_has_col($pdo,'users','locked_until')) {
      $pdo->prepare("UPDATE users SET failed_logins=0, locked_until=NULL WHERE id=?")
          ->execute([(int)$user['id']]);
    }
  }catch(Throwable $e){}

  $_SESSION['user'] = [
    'id' => (int)$user['id'],
    'name' => $user['name'] ?? ($user['username'] ?? 'Usuário'),
    'username' => $user['username'] ?? ($user['name'] ?? ''),
    'role' => $user['role'] ?? 'VIEWER',
    'password_changed_at' => $user['password_changed_at'] ?? null,
  ];
  log_access($pdo,'LOGIN_OK');
  return ['ok'=>true];
}

function do_logout(PDO $pdo): void {
  log_access($pdo,'LOGOUT');
  $_SESSION=[]; session_destroy();
}

/* ===================== SENHA ===================== */
function validate_password_complexity(string $pwd): ?string {
  if (strlen($pwd) < PWD_MIN_LEN) return "A senha deve ter pelo menos ".PWD_MIN_LEN." caracteres.";
  if (PWD_REQUIRE_UPPER  && !preg_match('/[A-Z]/', $pwd)) return "Inclua pelo menos 1 letra maiúscula.";
  if (PWD_REQUIRE_LOWER  && !preg_match('/[a-z]/', $pwd)) return "Inclua pelo menos 1 letra minúscula.";
  if (PWD_REQUIRE_DIGIT  && !preg_match('/\d/', $pwd))   return "Inclua pelo menos 1 número.";
  if (PWD_REQUIRE_SYMBOL && !preg_match('/[^A-Za-z0-9]/', $pwd)) return "Inclua pelo menos 1 símbolo.";
  return null;
}

function set_user_password(PDO $pdo, int $uid, string $pwd): void {
  $hash = password_hash($pwd, PASSWORD_DEFAULT);
  if (db_has_col($pdo,'users','password_changed_at')) {
    $pdo->prepare("UPDATE users SET password_hash=?, password_changed_at=NOW() WHERE id=?")
        ->execute([$hash,$uid]);
  } else {
    $pdo->prepare("UPDATE users SET password_hash=? WHERE id=?")
        ->execute([$hash,$uid]);
  }
  if (current_user() && (int)current_user()['id']===$uid){
    $_SESSION['user']['password_changed_at']=date('Y-m-d H:i:s');
    unset($_SESSION['force_pwd_change']);
  }
}

/* ===================== GUARDS ===================== */
function require_login(): void {
  if(!is_logged()){
    header('Location: '.base_url('login.php')); exit;
  }
  // força troca de senha ao expirar — só se tivermos a coluna/data
  if (!in_array(basename($_SERVER['PHP_SELF'] ?? ''), ['change_password.php','logout.php','login.php'], true)) {
    $changed = current_user()['password_changed_at'] ?? null;
    if ($changed) {
      $exp = strtotime("+".PASSWORD_EXPIRY_DAYS." days", strtotime($changed));
      if (time() > $exp) {
        $_SESSION['force_pwd_change'] = true;
        header('Location: '.base_url('change_password.php')); exit;
      }
    }
  }
}

function require_perm(string $perm): void {
  require_login();
  if(!user_has_perm($perm)){
    http_response_code(403);
    include __DIR__.'/header.php';
    echo '<div class="container"><div class="alert alert-danger">Acesso negado.</div></div>';
    include __DIR__.'/footer.php';
    exit;
  }
}

/* ===================== LABELS DE STATUS / AÇÕES (PT-BR) ===================== */
// Status genéricos (empréstimos, estoque e RR)
if (!function_exists('t_status')) {
  function t_status(?string $code): string {
    if (!$code) return '—';
    $code = strtoupper(trim($code));
    $map = [
      // Empréstimos
      'DISPONIBILIZADO' => 'Disponibilizado',
      'EMPRESTADO'      => 'Emprestado',
      'DEVOLVIDO'       => 'Devolvido',

      // Equipamentos
      'EM_ESTOQUE'      => 'Em estoque',
      'MANUTENCAO'      => 'Manutenção',
      'DEFEITO'         => 'Defeito',
      'BAIXADO'         => 'Baixado',
      'FURTO'           => 'Furto/Extravio',

      // Solicitações de devolução
      'ABERTA'          => 'Aberta',
      'EM_ANDAMENTO'    => 'Em andamento',
      'CONCLUIDA'       => 'Concluída',
      'CANCELADA'       => 'Cancelada',
      'COBRANCA'        => 'Cobrança',
    ];
    return $map[$code] ?? ucwords(strtolower(str_replace('_',' ', $code)));
  }
}
if (!function_exists('t_rr_status')) { function t_rr_status(?string $c): string { return t_status($c); } }
if (!function_exists('t_eq_status')) { function t_eq_status(?string $c): string { return t_status($c); } }

// Ações (logs)
if (!function_exists('t_action')) {
  function t_action(?string $code): string {
    if (!$code) return '—';
    $code = strtoupper(trim($code));
    $map = [
      'CREATE'                 => 'Cadastro inicial',
      'LOANED'                 => 'Emprestado',
      'STATUS_CHANGE'          => 'Mudança de status',
      'WRITE_OFF'              => 'Baixado',
      'RETURN_REQUEST_OPENED'  => 'RR aberto pelo RH',
      'RETURN_REQUEST_CLOSED'  => 'RR concluído',
      'PAGE'                   => 'Acesso',
      'LOGIN_OK'               => 'Login OK',
      'LOGIN_FAIL'             => 'Falha de login',
      'LOGOUT'                 => 'Logout',
    ];
    return $map[$code] ?? ucwords(strtolower(str_replace('_',' ', $code)));
  }
}

// Datas (entrega / previsão) — helpers simples
if (!function_exists('t_delivery')) {
  function t_delivery(?string $date): string { return $date ? br_date($date) : '—'; }
}
if (!function_exists('t_due')) {
  function t_due(?string $date): string { return $date ? br_date($date) : '—'; }
}

