<?php
/**
 * ThreatIpsTable — Cloud-synced global threat intelligence IP list.
 *
 * Rows are synced from the UltraGuard server via ServerCommunication::pull_blacklist().
 * The firewall module can fast-check this table at init priority 1 before any WAF logic.
 *
 * Source types:
 *   'cloud_blacklist'  — IPs that have attacked other UltraGuard secured sites
 *   'crowd_report'     — IPs reported by the community via ServerCommunication::push_event()
 *   'manual'           — Admin-entered via the IP Management page
 *
 * @package UltraguardPro\modules\db_tables\db
 * @since   6.6.0
 */

namespace UltraguardPro\modules\db_tables\db;

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

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

class ThreatIpsTable extends BaseTable {

    protected $table_name  = 'ultraguard_threat_ips';
    protected $cache_group = 'ultraguard_threat_ips';

    // ── BaseTable contract ─────────────────────────────────────────────────────

    public function get_defaults(): array {
        return [
            'id'           => 0,
            'ip_address'   => '',
            'cidr_range'   => null,
            'source'       => 'cloud_blacklist',
            'threat_level' => 'medium',
            'attack_types' => null,
            'report_count' => 1,
            'country_code' => null,
            'is_active'    => 1,
            'first_seen'   => '',
            'last_seen'    => '',
            'expires_at'   => null,
            'synced_at'    => '',
            'created_at'   => '',
            'updated_at'   => '',
        ];
    }

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

        // Validate IP (single address OR CIDR)
        $ip   = $data['ip_address'];
        $cidr = $data['cidr_range'] ?? null;

        if ( $cidr === null && ! filter_var( $ip, FILTER_VALIDATE_IP ) ) { return false; }

        $valid_sources = [ 'cloud_blacklist', 'crowd_report', 'manual' ];
        if ( isset( $data['source'] ) && ! in_array( $data['source'], $valid_sources, true ) ) {
            $data['source'] = 'cloud_blacklist';
        }

        return $data;
    }

    protected function sanitize_data( $data ): array {
        $out = [];
        foreach ( $data as $k => $v ) {
            $out[ $k ] = match ( $k ) {
                'attack_types' => is_null( $v ) ? null
                               : ( is_array( $v ) ? wp_json_encode( $v ) : sanitize_textarea_field( (string) $v ) ),
                'report_count' => max( 1, absint( $v ) ),
                'is_active'    => (int) (bool) $v,
                'expires_at',
                'first_seen',
                'last_seen',
                'synced_at'    => is_null( $v ) ? null : sanitize_text_field( (string) $v ),
                default        => is_string( $v ) ? sanitize_text_field( $v ) : $v,
            };
        }
        return $out;
    }

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

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

    /**
     * Fast check whether a single IP is in the global threat list.
     * Direct query — called on every request at init:1.
     *
     * @param  string $ip  IPv4 or IPv6.
     * @return bool
     */
    public function is_threat( string $ip ): bool {
        // 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'
        );
    }

    /**
     * Fetch all currently-active threat IPs as a flat array.
     * Cached in a transient to avoid DB hits on every request.
     *
     * @param  bool $force  Bypass transient cache.
     * @return string[]     Array of IP strings.
     */
    public function get_active_ips( bool $force = false ): array {
        $cache_key = 'ultraguard_threat_bl_cache';

        if ( ! $force ) {
            $cached = get_transient( $cache_key );
            if ( is_array( $cached ) ) { return $cached; }
        }

        $ips = DatabaseCrud::get_column(
            $this->table_name,
            'ip_address',
            [
                'where'        => [ 'is_active' => 1 ],
                'expire_field' => 'expires_at',   // (expires_at IS NULL OR expires_at >= now)
                'limit'        => 50000,
            ]
        );

        set_transient( $cache_key, $ips, HOUR_IN_SECONDS );
        return $ips;
    }

    /**
     * Insert or update a batch of entries received from the server.
     *
     * @param  array  $entries  Array of entry arrays from the API.
     * @param  string $source   Source label ('cloud_blacklist' | 'crowd_report').
     * @return int              Number of rows inserted/updated.
     */
    public function upsert_batch( array $entries, string $source = 'cloud_blacklist' ): int {
        $synced_at = current_time( 'mysql', true );
        $count     = 0;

        foreach ( $entries as $entry ) {
            $ip = sanitize_text_field( $entry['ip'] ?? $entry['ip_address'] ?? '' );
            if ( ! $ip ) { continue; }

            $existing = $this->get_row( [ 'ip_address' => $ip ] );

            $data = [
                'ip_address'   => $ip,
                'cidr_range'   => isset( $entry['cidr'] ) ? sanitize_text_field( $entry['cidr'] ) : null,
                'source'       => sanitize_key( $entry['source'] ?? $source ),
                'threat_level' => sanitize_key( $entry['threat_level'] ?? 'medium' ),
                'attack_types' => $entry['attack_types'] ?? null,
                'report_count' => max( 1, absint( $entry['report_count'] ?? 1 ) ),
                'country_code' => isset( $entry['country_code'] ) ? strtoupper( sanitize_text_field( $entry['country_code'] ) ) : null,
                'is_active'    => 1,
                'last_seen'    => sanitize_text_field( $entry['last_seen'] ?? $synced_at ),
                'expires_at'   => isset( $entry['expires_at'] ) ? sanitize_text_field( $entry['expires_at'] ) : null,
                'synced_at'    => $synced_at,
            ];

            if ( $existing && ! empty( $existing['id'] ) ) {
                // Increment report count and update
                $data['report_count'] = max( $data['report_count'], (int) $existing['report_count'] + 1 );
                $this->update( (int) $existing['id'], $data );
            } else {
                $data['first_seen'] = sanitize_text_field( $entry['first_seen'] ?? $synced_at );
                $this->insert( $data );
            }
            $count++;
        }

        // Invalidate the in-memory cache
        delete_transient( 'ultraguard_threat_bl_cache' );

        return $count;
    }

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

    /**
     * Summary for the dashboard / IP Management page.
     *
     * @return array{total:int, cloud:int, crowd:int, manual:int, active:int}
     */
    public function get_threat_summary(): array {
        return [
            'total'  => $this->count(),
            'cloud'  => $this->count( [ 'source' => 'cloud_blacklist', 'is_active' => 1 ] ),
            'crowd'  => $this->count( [ 'source' => 'crowd_report',    'is_active' => 1 ] ),
            'manual' => $this->count( [ 'source' => 'manual',          'is_active' => 1 ] ),
            'active' => $this->count( [ 'is_active' => 1 ] ),
        ];
    }
}
