<?php
/**
 * Hostiva v3 — API bootstrap
 * Sets up config, DB, session, security headers, JSON helpers.
 */
declare(strict_types=1);

// --- Locate config ---
$configPath = __DIR__ . '/../config/config.php';
if (!file_exists($configPath)) {
    $configPath = __DIR__ . '/../config/config.sample.php';
}
$CONFIG = require $configPath;

// --- Error handling: never leak HTML into JSON ---
error_reporting(E_ALL);
ini_set('display_errors', !empty($CONFIG['app']['debug']) ? '1' : '0');
ini_set('log_errors', '1');
ini_set('error_log', __DIR__ . '/../logs/php_errors.log');

if (!is_dir(__DIR__ . '/../logs')) {
    @mkdir(__DIR__ . '/../logs', 0755, true);
}

date_default_timezone_set($CONFIG['app']['timezone'] ?? 'UTC');

function app_base_path(): string {
    global $CONFIG;
    if (!empty($CONFIG['app']['base_path'])) {
        $p = rtrim((string)$CONFIG['app']['base_path'], '/');
        return $p === '' ? '' : $p;
    }
    $url = $CONFIG['app']['url'] ?? '';
    $path = parse_url($url, PHP_URL_PATH);
    if (!$path || $path === '/') return '';
    return rtrim($path, '/');
}

// --- Security headers ---
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('Referrer-Policy: strict-origin-when-cross-origin');
header('Permissions-Policy: geolocation=(), microphone=(), camera=()');
if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
    header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
}

// --- JSON helpers ---
function json_response($data, int $code = 200): void {
    http_response_code($code);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}
function json_ok($data = [], string $message = 'ok'): void {
    json_response(['success' => true, 'message' => $message, 'data' => $data]);
}
function json_error(string $message, int $code = 400, $extra = null): void {
    $payload = ['success' => false, 'message' => $message];
    if ($extra !== null) $payload['errors'] = $extra;
    json_response($payload, $code);
}

// --- Fatal error guard ---
register_shutdown_function(function () {
    global $CONFIG;
    $err = error_get_last();
    if ($err && in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true)) {
        error_log(sprintf('Fatal PHP error: %s in %s:%s', $err['message'], $err['file'], $err['line']));
        if (!headers_sent()) {
            http_response_code(500);
            header('Content-Type: application/json; charset=utf-8');
        }
        $payload = ['success' => false, 'message' => 'Server error'];
        if (!empty($CONFIG['app']['debug'])) {
            $payload['error'] = $err['message'];
        }
        echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    }
});

// --- Database (PDO) ---
function db(): PDO {
    global $CONFIG;
    static $pdo = null;
    if ($pdo) return $pdo;
    $dsn = sprintf('mysql:host=%s;port=%d;dbname=%s;charset=%s',
        $CONFIG['db']['host'], $CONFIG['db']['port'],
        $CONFIG['db']['name'], $CONFIG['db']['charset']);
    try {
        $pdo = new PDO($dsn, $CONFIG['db']['user'], $CONFIG['db']['pass'], [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false,
            PDO::ATTR_PERSISTENT         => false,
            PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4",
        ]);
    } catch (Throwable $e) {
        json_error('Database connection failed. Check config/config.php and database credentials.', 500);
    }
    return $pdo;
}

// --- Session ---
function start_session(): void {
    global $CONFIG;
    if (session_status() === PHP_SESSION_ACTIVE) return;

    ini_set('session.use_strict_mode', '1');
    ini_set('session.use_only_cookies', '1');
    ini_set('session.cookie_httponly', '1');
    ini_set('session.gc_maxlifetime', (string)($CONFIG['security']['session_lifetime'] ?? 7200));

    $sameSite = $CONFIG['security']['cookie_samesite'] ?? 'Lax';
    if (!in_array($sameSite, ['Lax', 'Strict', 'None'], true)) $sameSite = 'Lax';
    $secure = !empty($CONFIG['security']['cookie_secure']) || (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');

    session_name($CONFIG['security']['session_name'] ?? 'HOSTIVA_SID');
    $cookiePath = app_base_path() ?: '/';
    session_set_cookie_params([
        'lifetime' => (int)($CONFIG['security']['session_lifetime'] ?? 7200),
        'path'     => $cookiePath . '/',
        'secure'   => $secure,
        'httponly' => true,
        'samesite' => $sameSite,
    ]);
    session_start();
}

// --- CSRF ---
function csrf_token(): string {
    start_session();
    if (empty($_SESSION['csrf'])) {
        $_SESSION['csrf'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf'];
}
function csrf_check(): void {
    start_session();
    $method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
    if (in_array($method, ['GET','HEAD','OPTIONS'], true)) return;
    $sent = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ($_POST['_csrf'] ?? '');
    if (!isset($_SESSION['csrf']) || !hash_equals($_SESSION['csrf'], (string)$sent)) {
        json_error('Invalid CSRF token', 419);
    }
}

// --- Auth helpers ---
function current_user(): ?array {
    start_session();
    if (empty($_SESSION['user_id'])) return null;
    static $cached = null;
    if ($cached && (int)$cached['id'] === (int)$_SESSION['user_id']) return $cached;
    $stmt = db()->prepare('SELECT u.*, r.name AS role_name, r.label AS role_label FROM users u LEFT JOIN roles r ON r.id=u.role_id WHERE u.id=? AND u.deleted_at IS NULL LIMIT 1');
    $stmt->execute([$_SESSION['user_id']]);
    $cached = $stmt->fetch() ?: null;
    return $cached;
}
function require_auth(): array {
    $u = current_user();
    if (!$u) json_error('Unauthorized', 401);
    if ($u['status'] !== 'active') json_error('Account is ' . $u['status'], 403);
    return $u;
}
function user_permissions(?array $u = null): array {
    $u = $u ?: current_user();
    if (!$u || empty($u['role_id'])) return [];
    static $cache = [];
    $roleId = (int)$u['role_id'];
    if (isset($cache[$roleId])) return $cache[$roleId];
    if (($u['role_name'] ?? '') === 'super_admin') {
        $rows = db()->query('SELECT code FROM permissions ORDER BY code')->fetchAll(PDO::FETCH_COLUMN);
        return $cache[$roleId] = array_values($rows ?: []);
    }
    $stmt = db()->prepare('SELECT p.code FROM role_permissions rp JOIN permissions p ON p.id=rp.permission_id WHERE rp.role_id=? ORDER BY p.code');
    $stmt->execute([$roleId]);
    return $cache[$roleId] = array_values($stmt->fetchAll(PDO::FETCH_COLUMN) ?: []);
}
function permission_aliases(): array {
    return [
        'bookings.edit' => 'bookings.manage', 'bookings.create' => 'bookings.manage', 'bookings.cancel' => 'bookings.manage',
        'properties.edit' => 'properties.manage', 'properties.create' => 'properties.manage', 'properties.delete' => 'properties.manage',
        'finance.edit' => 'finance.manage', 'users.edit' => 'users.manage', 'users.create' => 'users.manage', 'users.delete' => 'users.manage',
        'tickets.edit' => 'tickets.manage', 'tickets.create' => 'tickets.manage',
        'reports.export' => 'reports.view', 'dashboard.view' => 'bookings.view',
        'channels.edit' => 'channels.manage', 'channels.create' => 'channels.manage',
        'calendar.edit' => 'calendar.manage', 'ota.edit' => 'ota.manage',
        'inbox.edit' => 'inbox.manage', 'automations.edit' => 'automations.manage',
    ];
}
function has_permission(string $code): bool {
    $u = current_user();
    if (!$u || !$u['role_id']) return false;
    if (($u['role_name'] ?? '') === 'super_admin') return true;
    $perms = user_permissions($u);
    if (in_array($code, $perms, true)) return true;
    $aliases = permission_aliases();
    if (isset($aliases[$code]) && in_array($aliases[$code], $perms, true)) return true;
    return false;
}
function has_any_permission(array $codes): bool {
    foreach ($codes as $code) {
        if (has_permission($code)) return true;
    }
    return false;
}
function require_permission(string $code): void {
    require_auth();
    if (!has_permission($code)) json_error('Forbidden: missing permission ' . $code, 403);
}
function require_any_permission(array $codes): void {
    require_auth();
    if (!has_any_permission($codes)) json_error('Forbidden', 403);
}
function expose_user(array $u): array {
    unset($u['password'], $u['remember_token']);
    $u['permissions'] = user_permissions($u);
    return $u;
}

// --- Rate limiting (simple per-session window) ---
function rate_limit(string $bucket, int $maxPerMinute = 60): void {
    start_session();
    $key = 'rl_' . $bucket;
    $now = time();
    $window = $_SESSION[$key] ?? ['start' => $now, 'count' => 0];
    if ($now - $window['start'] > 60) $window = ['start' => $now, 'count' => 0];
    $window['count']++;
    $_SESSION[$key] = $window;
    if ($window['count'] > $maxPerMinute) {
        json_error('Too many requests', 429);
    }
}

// --- Audit log ---
function audit(string $action, ?string $entityType = null, $entityId = null, ?string $description = null, $meta = null): void {
    try {
        $u = current_user();
        $stmt = db()->prepare('INSERT INTO activity_log (user_id, action, entity_type, entity_id, description, ip, user_agent, meta) VALUES (?,?,?,?,?,?,?,?)');
        $stmt->execute([
            $u['id'] ?? null,
            $action,
            $entityType,
            $entityId,
            $description,
            $_SERVER['REMOTE_ADDR'] ?? null,
            substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 250),
            $meta ? json_encode($meta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null,
        ]);
    } catch (Throwable $e) { /* never block on audit */ }
}

// --- Request body parser ---
function input(): array {
    $ct = $_SERVER['CONTENT_TYPE'] ?? '';
    if (stripos($ct, 'application/json') !== false) {
        $raw = file_get_contents('php://input');
        $data = json_decode($raw, true);
        return is_array($data) ? $data : [];
    }
    return array_merge($_GET, $_POST);
}

// --- Validation helpers ---
function v_required(array $data, array $fields): array {
    $errors = [];
    foreach ($fields as $f) {
        if (!isset($data[$f]) || (is_string($data[$f]) && trim($data[$f]) === '')) {
            $errors[$f] = 'Required';
        }
    }
    return $errors;
}
function v_email(string $email): bool {
    return (bool)filter_var($email, FILTER_VALIDATE_EMAIL);
}
function v_str(array $data, string $field, int $max = 255): string {
    return substr(trim((string)($data[$field] ?? '')), 0, $max);
}
function v_date($value, string $field): string {
    $value = trim((string)$value);
    $dt = DateTime::createFromFormat('!Y-m-d', $value);
    if (!$dt || $dt->format('Y-m-d') !== $value) json_error('Invalid ' . $field . ' date', 422);
    return $value;
}
function v_enum($value, array $allowed, string $field, ?string $default = null): string {
    if ($value === null || $value === '') return $default ?? $allowed[0];
    $value = (string)$value;
    if (!in_array($value, $allowed, true)) json_error('Invalid ' . $field, 422);
    return $value;
}
function v_positive_number($value, string $field): float {
    if (!is_numeric($value) || (float)$value <= 0) json_error($field . ' must be greater than zero', 422);
    return (float)$value;
}

// V4 STR OS layer (additive — skipped if files missing)
$__hv4 = __DIR__ . '/../includes/v4/bootstrap.php';
if (is_file($__hv4)) {
    require_once $__hv4;
}

// V5 ecosystem layer (additive)
$__hv5 = __DIR__ . '/../includes/v5/bootstrap.php';
if (is_file($__hv5)) {
    require_once $__hv5;
}

start_session();


// v6 audit helper — safe no-op writer; writes to activity_log if table exists
if (!function_exists('audit_log')) {
    function audit_log(string $action, string $entity = null, $entity_id = null, $meta = null): void {
        try {
            $u = current_user();
            $uid = $u['id'] ?? null;
            $stmt = db()->prepare('INSERT INTO activity_log (user_id, action, entity, entity_id, meta, created_at) VALUES (?, ?, ?, ?, ?, NOW())');
            $stmt->execute([
                $uid,
                $action,
                $entity,
                $entity_id !== null ? (int)$entity_id : null,
                is_array($meta) ? json_encode($meta) : $meta,
            ]);
        } catch (Throwable $e) {
            // silent — audit is best-effort
        }
    }
}
