<?php

if (!defined('ABSPATH')) {
    exit;
}

class BS2FA_Rate_Limit {
    public static function enabled() {
        return (bool) BS2FA_Helpers::get_option('rate_limit_enabled', 1);
    }

    protected static function key($scope, $identity) {
        $fingerprint = BS2FA_Helpers::context_hash($scope . '|' . strtolower((string) $identity) . '|' . BS2FA_Helpers::client_ip());
        return 'bs2fa_rl_' . $scope . '_' . $fingerprint;
    }

    protected static function window_minutes($scope) {
        return $scope === 'primary'
            ? max(1, (int) BS2FA_Helpers::get_option('primary_lockout_minutes', 15))
            : max(1, (int) BS2FA_Helpers::get_option('challenge_lockout_minutes', 10));
    }

    protected static function max_attempts($scope) {
        return $scope === 'primary'
            ? max(2, (int) BS2FA_Helpers::get_option('primary_max_attempts', 5))
            : max(2, (int) BS2FA_Helpers::get_option('challenge_max_attempts', 5));
    }

    protected static function read_state($scope, $identity) {
        $state = get_transient(self::key($scope, $identity));
        return is_array($state) ? $state : ['count' => 0, 'first' => 0, 'lock_until' => 0];
    }

    protected static function write_state($scope, $identity, $state) {
        $ttl = max(self::window_minutes($scope), 1) * MINUTE_IN_SECONDS;
        if (!empty($state['lock_until'])) {
            $ttl = max($ttl, ((int) $state['lock_until'] - time()) + MINUTE_IN_SECONDS);
        }

        set_transient(self::key($scope, $identity), $state, max(MINUTE_IN_SECONDS, $ttl));
    }

    public static function is_blocked($scope, $identity) {
        if (!self::enabled()) {
            return false;
        }

        $state = self::read_state($scope, $identity);
        return !empty($state['lock_until']) && (int) $state['lock_until'] > time();
    }

    public static function remaining_seconds($scope, $identity) {
        $state = self::read_state($scope, $identity);
        if (empty($state['lock_until'])) {
            return 0;
        }

        return max(0, (int) $state['lock_until'] - time());
    }

    public static function note_failure($scope, $identity) {
        if (!self::enabled()) {
            return 0;
        }

        $state = self::read_state($scope, $identity);
        $now = time();
        $window_seconds = self::window_minutes($scope) * MINUTE_IN_SECONDS;
        $max_attempts = self::max_attempts($scope);

        if (empty($state['first']) || ($now - (int) $state['first']) > $window_seconds) {
            $state = ['count' => 0, 'first' => $now, 'lock_until' => 0];
        }

        $state['count'] = (int) $state['count'] + 1;
        if ((int) $state['count'] >= $max_attempts) {
            $state['lock_until'] = $now + $window_seconds;
            $state['count'] = 0;
            $state['first'] = $now;
        }

        self::write_state($scope, $identity, $state);

        return (int) ($state['lock_until'] ?? 0);
    }

    public static function clear($scope, $identity) {
        delete_transient(self::key($scope, $identity));
    }

    public static function primary_identity($username) {
        return strtolower(trim((string) $username));
    }

    public static function challenge_identity($user_id) {
        return 'user:' . (int) $user_id;
    }
}
