<?php

declare(strict_types=1);

namespace SiteFort\Modules\Cloudflare;

// If this file is called directly, abort.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

use SiteFort\Includes\Interfaces\Module;
use SiteFort\Includes\Module_Loader;

/**
 * Cloudflare integration controller.
 *
 * Routes AJAX/UI actions and plugin events into the Cloudflare Manager.
 */
class Cloudflare implements Module {

    private string $plugin_name;
    private string $version;
    private Manager $manager;

    public function __construct( string $plugin_name, string $version ) {
        $this->plugin_name = $plugin_name;
        $this->version = $version;
        $this->manager = new Manager();
    }

    public function initialize( Module_Loader $loader ) {
        // Internal cron reconciliation.
        $loader->add_action( Manager::CRON_HOOK, $this, 'run_reconcile_job', 10, 0 );

        // Firewall control-plane events. Runtime traffic is promoted
        // to Cloudflare only when the auto-block policy threshold is reached.
        $loader->add_action( 'sitefort_firewall_block', $this, 'on_firewall_block', 10, 1 );
        $loader->add_action( 'sitefort_firewall_lockout_changed', $this, 'on_firewall_lockout_changed', 10, 1 );
        $loader->add_action( 'sitefort_settings_saved_firewall', $this, 'on_firewall_settings_saved', 10, 1 );

        // Backward-compatible hooks.
        $loader->add_action( 'sitefort_block_ip', $this, 'handle_block_ip', 10, 2 );
        $loader->add_action( 'sitefort_unblock_ip', $this, 'handle_unblock_ip', 10, 1 );

        // AJAX endpoints used by admin app.
        $loader->add_action( 'wp_ajax_sitefort_cf_get_settings', $this, 'ajax_get_settings' );
        $loader->add_action( 'wp_ajax_sitefort_cf_verify', $this, 'ajax_verify' );
        $loader->add_action( 'wp_ajax_sitefort_cf_save_settings', $this, 'ajax_save_settings' );
        $loader->add_action( 'wp_ajax_sitefort_cf_save_policy', $this, 'ajax_save_policy' );
        $loader->add_action( 'wp_ajax_sitefort_cf_sync', $this, 'ajax_sync' );
        $loader->add_action( 'wp_ajax_sitefort_cf_block_ip', $this, 'ajax_block_ip' );
        $loader->add_action( 'wp_ajax_sitefort_cf_unblock_ip', $this, 'ajax_unblock_ip' );
    }

    public function run_reconcile_job(): void {
        $result = $this->manager->reconcile();
        do_action( 'sitefort_cloudflare_action', is_wp_error( $result ) ? 'sync_failed' : 'sync', '' );
    }

    /**
     * @param array<string,mixed> $event
     */
    public function on_firewall_block( array $event ): void {
        $this->manager->on_firewall_block( $event );
    }

    /**
     * @param array<string,mixed> $payload
     */
    public function on_firewall_lockout_changed( array $payload ): void {
        $this->manager->on_firewall_lockout_changed( $payload );
    }

    /**
     * @param array<string,mixed> $settings
     */
    public function on_firewall_settings_saved( array $settings ): void {
        if ( empty( $settings['enabled'] ) ) {
            $cloudflare_settings = \SiteFort\Includes\SettingsStore::get( \SiteFort\Includes\SettingsStore::COL_CLOUDFLARE, [] );
            if ( ! empty( $cloudflare_settings['enabled'] ) ) {
                $this->manager->save_policy( [ 'enabled' => false ] );
            }
        }

        $this->manager->schedule_reconcile( 20 );
    }

    public function handle_block_ip( string $ip, string $reason = '' ): void {
        $result = $this->manager->add_manual_entry( $ip, 'block' );
        if ( ! is_wp_error( $result ) ) {
            do_action( 'sitefort_cloudflare_action', 'block_ip', $ip );
        }
    }

    public function handle_unblock_ip( string $ip ): void {
        $result = $this->manager->remove_manual_entry( $ip, 'both' );
        if ( ! is_wp_error( $result ) ) {
            do_action( 'sitefort_cloudflare_action', 'unblock_ip', $ip );
        }
    }

    public function ajax_get_settings(): void {
        if ( ! $this->verify_ajax_request() ) {
            return;
        }

        wp_send_json_success( $this->manager->get_admin_settings() );
    }

    public function ajax_verify(): void {
        if ( ! $this->verify_ajax_request() ) {
            return;
        }

        $result = $this->manager->verify_connection();
        if ( is_wp_error( $result ) ) {
            $data = $result->get_error_data();
            wp_send_json_error( [
                'message' => $result->get_error_message(),
                'code'    => $result->get_error_code(),
                'settings' => is_array( $data ) ? ( $data['settings'] ?? null ) : null,
                'permission_check' => is_array( $data ) ? ( $data['permission_check'] ?? null ) : null,
                'documentation_url' => is_array( $data ) ? ( $data['documentation_url'] ?? null ) : null,
                'resource' => is_array( $data ) ? ( $data['resource'] ?? null ) : null,
            ] );
            return;
        }

        wp_send_json_success( [
            'message'  => is_array( $result ) ? ( $result['message'] ?? 'Cloudflare credentials verified.' ) : 'Cloudflare credentials verified.',
            'settings' => is_array( $result ) ? ( $result['settings'] ?? $this->manager->get_admin_settings() ) : $this->manager->get_admin_settings(),
            'result'   => is_array( $result ) ? ( $result['result'] ?? $result ) : $result,
        ] );
    }

    public function ajax_save_settings(): void {
        if ( ! $this->verify_ajax_request() ) {
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified by verify_ajax_request() before reading request data.
        $zone_id = sanitize_text_field( wp_unslash( $_POST['zone_id'] ?? '' ) );
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified by verify_ajax_request() before reading request data.
        $api_token = trim( sanitize_text_field( (string) wp_unslash( $_POST['api_token'] ?? '' ) ) );
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified by verify_ajax_request() before reading request data.
        $auth_mode = sanitize_key( (string) wp_unslash( $_POST['auth_mode'] ?? 'api_token' ) );
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified by verify_ajax_request() before reading request data.
        $auth_email = sanitize_email( (string) wp_unslash( $_POST['auth_email'] ?? '' ) );
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified by verify_ajax_request() before reading request data.
        $global_api_key = trim( sanitize_text_field( (string) wp_unslash( $_POST['global_api_key'] ?? '' ) ) );

        $result = $this->manager->save_connection( $zone_id, $api_token, $auth_mode, $auth_email, $global_api_key );
        if ( is_wp_error( $result ) ) {
            $data = $result->get_error_data();
            wp_send_json_error( [
                'message' => $result->get_error_message(),
                'code'    => $result->get_error_code(),
                'settings' => is_array( $data ) ? ( $data['settings'] ?? null ) : null,
                'permission_check' => is_array( $data ) ? ( $data['permission_check'] ?? null ) : null,
                'documentation_url' => is_array( $data ) ? ( $data['documentation_url'] ?? null ) : null,
                'resource' => is_array( $data ) ? ( $data['resource'] ?? null ) : null,
            ] );
            return;
        }

        wp_send_json_success( $result );
    }

    public function ajax_save_policy(): void {
        if ( ! $this->verify_ajax_request() ) {
            return;
        }

        $policy = $this->parse_json_post_array( 'policy' );
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified by verify_ajax_request() before reading request data.
        if ( isset( $_POST['enabled'] ) ) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified by verify_ajax_request() before reading request data.
            $policy['enabled'] = ! empty( $_POST['enabled'] );
        }

        $settings = $this->manager->save_policy( $policy );

        wp_send_json_success( [
            'message'  => 'Cloudflare policy saved.',
            'settings' => $settings,
        ] );
    }

    public function ajax_sync(): void {
        if ( ! $this->verify_ajax_request() ) {
            return;
        }

        $result = $this->manager->reconcile( true );
        if ( is_wp_error( $result ) ) {
            wp_send_json_error( [
                'message' => $result->get_error_message(),
                'code'    => $result->get_error_code(),
                'data'    => $result->get_error_data(),
            ] );
            return;
        }

        do_action( 'sitefort_cloudflare_action', 'sync', '' );
        wp_send_json_success( $result );
    }

    public function ajax_block_ip(): void {
        if ( ! $this->verify_ajax_request() ) {
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified by verify_ajax_request() before reading request data.
        $entry = sanitize_text_field( (string) wp_unslash( $_POST['ip'] ?? '' ) );
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified by verify_ajax_request() before reading request data.
        $list_type = sanitize_key( (string) wp_unslash( $_POST['list_type'] ?? 'block' ) );
        $list_type = $list_type === 'allow' ? 'allow' : 'block';

        $result = $this->manager->add_manual_entry( $entry, $list_type );
        if ( is_wp_error( $result ) ) {
            wp_send_json_error( [
                'message' => $result->get_error_message(),
                'code'    => $result->get_error_code(),
            ] );
            return;
        }

        do_action( 'sitefort_cloudflare_action', $list_type === 'allow' ? 'allow_ip' : 'block_ip', $entry );
        wp_send_json_success( $result );
    }

    public function ajax_unblock_ip(): void {
        if ( ! $this->verify_ajax_request() ) {
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified by verify_ajax_request() before reading request data.
        $entry = sanitize_text_field( (string) wp_unslash( $_POST['ip'] ?? '' ) );
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified by verify_ajax_request() before reading request data.
        $list_type = sanitize_key( (string) wp_unslash( $_POST['list_type'] ?? 'both' ) );
        if ( ! in_array( $list_type, [ 'block', 'allow', 'both' ], true ) ) {
            $list_type = 'both';
        }

        $result = $this->manager->remove_manual_entry( $entry, $list_type );
        if ( is_wp_error( $result ) ) {
            wp_send_json_error( [
                'message' => $result->get_error_message(),
                'code'    => $result->get_error_code(),
            ] );
            return;
        }

        do_action( 'sitefort_cloudflare_action', 'unblock_ip', $entry );
        wp_send_json_success( $result );
    }

    private function verify_ajax_request(): bool {
        check_ajax_referer( 'sitefort_nonce', '_ajax_nonce' );

        if ( ! \SiteFort\Includes\Capabilities::can_manage_plugin() ) {
            wp_send_json_error( [ 'message' => 'Unauthorized' ] );
            return false;
        }

        return true;
    }

    /**
     * @return array<string,mixed>
     */
    private function parse_json_post_array( string $key ): array {
        // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce is verified before reading request data, and sanitize_post_value() normalizes the resulting value immediately.
        $raw = $this->sanitize_post_value( wp_unslash( $_POST[ $key ] ?? '' ) );
        if ( is_string( $raw ) && $raw !== '' ) {
            $decoded = json_decode( $raw, true );
            if ( is_array( $decoded ) ) {
                return $this->sanitize_post_value( $decoded );
            }
        }

        if ( is_array( $raw ) ) {
            return $raw;
        }

        return [];
    }

    /**
     * Sanitize scalar and nested request data before handing it to the manager layer.
     *
     * @param mixed $value Request value.
     * @return mixed
     */
    private function sanitize_post_value( $value ) {
        if ( is_array( $value ) ) {
            foreach ( $value as $array_key => $array_value ) {
                $value[ $array_key ] = $this->sanitize_post_value( $array_value );
            }

            return $value;
        }

        if ( is_string( $value ) ) {
            return sanitize_text_field( $value );
        }

        return $value;
    }
}
