<?php
/**
 * LlBlockedIpsTable — standalone class file.
 *
 * @package UltraguardPro\modules\loginlimiter\db
 */

namespace UltraguardPro\modules\loginlimiter\db;

use UltraguardPro\db\BaseTable;
use UltraguardPro\core\DatabaseCrud;

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

/**
 * LlBlockedIpsTable — Module-owned IP block list.
 *
 * Fully self-contained — does not reference the Firewall module's ip_blacklist table.
 * Provides block/unblock and fast is_blocked() check for login-page enforcement.
 *
 * @package UltraguardPro\modules\loginlimiter\db
 * @since   6.5.0
 */
class LlBlockedIpsTable extends BaseTable {

    protected $table_name  = 'ultraguard_ll_blocked_ips';
    protected $cache_group = 'ultraguard_ll_blocked_ips';

    public function get_defaults(): array {
        return [
            'id'           => 0,
            'ip_address'   => '',
            'reason'       => 'manual',
            'block_type'   => 'manual',
            'threat_level' => 'medium',
            'blocked_by'   => 0,
            'expires_at'   => null,
            'is_active'    => 1,
            'created_at'   => '',
            'updated_at'   => '',
        ];
    }

    protected function validate_data( $data ) {
        if ( empty( $data['ip_address'] ) ) { return false; }
        if ( ! filter_var( $data['ip_address'], FILTER_VALIDATE_IP ) ) { return false; }

        $valid_threats = [ 'low', 'medium', 'high', 'critical' ];
        if ( isset( $data['threat_level'] ) && ! in_array( $data['threat_level'], $valid_threats, true ) ) {
            $data['threat_level'] = 'medium';
        }

        return $data;
    }

    protected function sanitize_data( $data ): array {
        $out = [];
        foreach ( $data as $k => $v ) {
            $out[ $k ] = match ( $k ) {
                'blocked_by' => absint( $v ),
                'is_active'  => (int) (bool) $v,
                'expires_at' => is_null( $v ) ? null : sanitize_text_field( (string) $v ),
                'reason'     => sanitize_text_field( substr( (string) $v, 0, 200 ) ),
                default      => is_string( $v ) ? sanitize_text_field( $v ) : $v,
            };
        }
        return $out;
    }

    protected function get_searchable_fields(): array {
        return [ 'ip_address', 'reason', 'threat_level' ];
    }

    // ── Domain methods ─────────────────────────────────────────────────────────

    /**
     * Fast check whether an IP is actively blocked.
     * Direct query — called on every login_init.
     *
     * @param  string $ip
     * @return bool
     */
    public function is_blocked( string $ip ): bool {
        // Delegates to DatabaseCrud which applies the nullable-expiry pattern:
        //   WHERE ip_address=? AND is_active=1 AND (expires_at IS NULL OR expires_at >= now)
        return DatabaseCrud::record_exists_not_expired(
            $this->table_name,
            [ 'ip_address' => $ip, 'is_active' => 1 ],
            'expires_at'
        );
    }

    /**
     * Block an IP address.
     *
     * @param  string      $ip
     * @param  string      $reason
     * @param  string      $block_type   'manual' | 'auto' | 'bulk'
     * @param  string      $threat_level 'low' | 'medium' | 'high' | 'critical'
     * @param  string|null $expires_at   MySQL datetime or null for permanent.
     * @return int|false   New row ID or false.
     */
    public function block_ip(
        string $ip,
        string $reason      = 'manual',
        string $block_type  = 'manual',
        string $threat_level= 'medium',
        ?string $expires_at = null
    ): int|false {
        // Update if already exists
        $existing = $this->get_row( [ 'ip_address' => $ip ] );
        if ( $existing && ! empty( $existing['id'] ) ) {
            $this->update( (int) $existing['id'], [
                'reason'       => $reason,
                'threat_level' => $threat_level,
                'is_active'    => 1,
                'expires_at'   => $expires_at,
            ] );
            return (int) $existing['id'];
        }

        return $this->insert( [
            'ip_address'   => $ip,
            'reason'       => $reason,
            'block_type'   => $block_type,
            'threat_level' => $threat_level,
            'blocked_by'   => get_current_user_id(),
            'expires_at'   => $expires_at,
            'is_active'    => 1,
        ] );
    }

    /**
     * Remove / deactivate a block by ID.
     *
     * @param  int $id
     * @return bool
     */
    public function unblock( int $id ): bool {
        return $this->update( $id, [ 'is_active' => 0 ] );
    }

    /**
     * Clear expired block entries.
     *
     * @return int  Rows deactivated.
     */
    public function clear_expired(): int {
        return $this->update_where(
            [ 'is_active' => 0 ],
            [ 'expires_at <' => gmdate( 'Y-m-d H:i:s' ), 'is_active' => 1 ]
        );
    }

    /**
     * Stat snapshot for dashboard/IP-management page.
     *
     * @return array{total:int, permanent:int, temporary:int}
     */
    public function get_block_summary(): array {
        return [
            'total'     => $this->count( [ 'is_active' => 1 ] ),
            'permanent' => $this->count( [ 'is_active' => 1, 'expires_at' => null ] ),
        ];
    }
}


/**
 * LlAllowedIpsTable — Module-owned IP allow list.
 *
 * IPs on this list are exempt from lockout enforcement and brute-force counting.
 *
 * @package UltraguardPro\modules\loginlimiter\db
 * @since   6.5.0
 */
