<?php

function cmsLoginSecurityDirectory()
{
    return dirname(__DIR__, 2) . '/data/cache/login-security';
}

function cmsLoginClientIp()
{
    $ip = trim((string) ($_SERVER['REMOTE_ADDR'] ?? ''));
    return filter_var($ip, FILTER_VALIDATE_IP) ? $ip : 'unknown';
}

function cmsLoginSecurityKey($scope, $value)
{
    $value = $scope === 'account' ? strtolower(trim((string) $value)) : trim((string) $value);
    return hash('sha256', $scope . ':' . $value);
}

function cmsLoginSecurityMutate($scope, $value, callable $callback, $directory = null)
{
    $directory = $directory ?: cmsLoginSecurityDirectory();
    if (!is_dir($directory) && !mkdir($directory, 0755, true) && !is_dir($directory)) {
        return ['attempts' => [], 'locked_until' => 0];
    }

    $file = rtrim($directory, '/\\') . DIRECTORY_SEPARATOR . cmsLoginSecurityKey($scope, $value) . '.json';
    $handle = fopen($file, 'c+');
    if (!$handle || !flock($handle, LOCK_EX)) {
        if ($handle) { fclose($handle); }
        return ['attempts' => [], 'locked_until' => 0];
    }

    $raw = stream_get_contents($handle);
    $state = json_decode((string) $raw, true);
    if (!is_array($state)) {
        $state = ['attempts' => [], 'locked_until' => 0];
    }
    $state = $callback($state);
    rewind($handle);
    ftruncate($handle, 0);
    fwrite($handle, json_encode($state, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
    fflush($handle);
    flock($handle, LOCK_UN);
    fclose($handle);
    return $state;
}

function cmsLoginSecurityClean(array $state, $now, $window = 900)
{
    $attempts = is_array($state['attempts'] ?? null) ? $state['attempts'] : [];
    $state['attempts'] = array_values(array_filter($attempts, function ($time) use ($now, $window) {
        return intval($time) > $now - $window;
    }));
    $state['locked_until'] = max(0, intval($state['locked_until'] ?? 0));
    if ($state['locked_until'] <= $now) {
        $state['locked_until'] = 0;
    }
    return $state;
}

function cmsGetLoginLock($username, $ip = null, $directory = null, $now = null)
{
    $now = $now ?: time();
    $ip = $ip ?: cmsLoginClientIp();
    foreach ([['ip', $ip], ['account', $username]] as $item) {
        if ($item[1] === '') { continue; }
        $state = cmsLoginSecurityMutate($item[0], $item[1], function ($state) use ($now) {
            return cmsLoginSecurityClean($state, $now);
        }, $directory);
        if (intval($state['locked_until'] ?? 0) > $now) {
            return ['locked' => true, 'retry_after' => intval($state['locked_until']) - $now, 'scope' => $item[0]];
        }
    }
    return ['locked' => false, 'retry_after' => 0, 'scope' => ''];
}

function cmsRecordLoginFailure($username, $ip = null, $directory = null, $now = null)
{
    $now = $now ?: time();
    $ip = $ip ?: cmsLoginClientIp();
    $locked = false;
    foreach ([['ip', $ip, 20], ['account', $username, 5]] as $item) {
        if ($item[1] === '') { continue; }
        $state = cmsLoginSecurityMutate($item[0], $item[1], function ($state) use ($now, $item) {
            $state = cmsLoginSecurityClean($state, $now);
            $state['attempts'][] = $now;
            if (count($state['attempts']) >= $item[2]) {
                $state['locked_until'] = $now + 900;
            }
            return $state;
        }, $directory);
        $locked = $locked || intval($state['locked_until'] ?? 0) > $now;
    }
    return $locked;
}

function cmsClearLoginFailures($username, $ip = null, $directory = null)
{
    $directory = $directory ?: cmsLoginSecurityDirectory();
    $ip = $ip ?: cmsLoginClientIp();
    foreach ([['ip', $ip], ['account', $username]] as $item) {
        if ($item[1] === '') { continue; }
        $file = rtrim($directory, '/\\') . DIRECTORY_SEPARATOR . cmsLoginSecurityKey($item[0], $item[1]) . '.json';
        if (is_file($file)) { @unlink($file); }
    }
}

function cmsWriteLoginSecurityLog($action, $username, $description = '')
{
    try {
        addLog($action, '登录安全', trim($description . ' | 账号: ' . (string) $username));
    } catch (Throwable $e) {
        error_log('EnterpriseCMS login security log failed: ' . $e->getMessage());
    }
}

