<?php
namespace Bookaro\Service;

defined( 'ABSPATH' ) || exit;

use Bookaro\Repository\BookingRepository;
use Bookaro\Utils\RateLimiter;
use Bookaro\Utils\Logger;

class BookingActionService
{
    private BookingService $bookings;
    private BookingRepository $repo;

    public function __construct( BookingService $bookings, BookingRepository $repo )
    {
        $this->bookings = $bookings;
        $this->repo = $repo;
    }

    private function error( string $code, string $message, int $status = 400 ): \WP_Error
    {
        return new \WP_Error( $code, $message, [ 'status' => $status ] );
    }

    public function sanitize_booking_payload( array $payload, array $context = [] ): array
    {
        $price_raw = sanitize_text_field( $payload['price'] ?? '' );
        $context = is_array( $context ) ? $context : [];
        $honeypot_value = '';
        foreach ( [ 'bookaro_honeypot', 'bookaro_hp_website', 'bookaro_website', 'website' ] as $honeypot_key ) {
            if ( ! array_key_exists( $honeypot_key, $payload ) ) {
                continue;
            }
            $raw_honeypot = $payload[ $honeypot_key ];
            if ( is_scalar( $raw_honeypot ) ) {
                $honeypot_value = sanitize_text_field( (string) $raw_honeypot );
                break;
            }
        }
        $form_started_raw = $payload['bookaro_form_started_at'] ?? $payload['form_started_at'] ?? 0;
        $form_elapsed_raw = $payload['bookaro_form_elapsed_seconds'] ?? $payload['form_elapsed_seconds'] ?? 0;

        $sanitized = [
            'booking_id'     => absint( $payload['booking_id'] ?? 0 ),
            'listing_id'      => absint( $payload['listing_id'] ?? 0 ),
            'provider_id'     => absint( $payload['provider_id'] ?? 0 ),
            'service_id'      => absint( $payload['service_id'] ?? 0 ),
            'service_label'   => sanitize_text_field( $payload['service_label'] ?? '' ),
            'service_token'   => sanitize_text_field( $payload['service_token'] ?? '' ),
            'service_duration'=> absint( $payload['service_duration'] ?? 0 ),
            'booking_date'    => sanitize_text_field( $payload['booking_date'] ?? '' ),
            'booking_date_to' => sanitize_text_field( $payload['booking_date_to'] ?? '' ),
            'time'            => sanitize_text_field( $payload['time'] ?? '' ),
            'name'            => sanitize_text_field( $payload['name'] ?? '' ),
            'email'           => sanitize_email( $payload['email'] ?? '' ),
            'phone'           => sanitize_text_field( $payload['phone'] ?? '' ),
            'price'           => $price_raw === '' ? 0.0 : (float) $price_raw,
            'notes'           => sanitize_textarea_field( $payload['notes'] ?? '' ),
            'spam_honeypot'   => $honeypot_value,
            'spam_form_started_at' => absint( $form_started_raw ),
            'spam_form_elapsed_seconds' => absint( $form_elapsed_raw ),
        ];

        return apply_filters( 'bookaro_booking_payload_sanitized', $sanitized, $payload, $context );
    }

    public function create_booking( array $payload, int $user_id )
    {
        // Bug 53: if the admin has flipped bookaro_settings.require_login = 0,
        // anonymous bookings are intentionally allowed. The REST permission
        // callback (can_create_booking) already mirrors this setting and only
        // demands a valid wp_rest nonce when anonymous. Mirror the same gate
        // here so the service layer agrees with the controller layer instead
        // of unconditionally rejecting user_id=0 with bookaro_auth_required.
        $require_login = (bool) bookaro_get_option( 'require_login', 1 );
        if ( $user_id < 1 ) {
            if ( $require_login ) {
                return $this->error(
                    'bookaro_auth_required',
                    __( 'You must be logged in to book a service.', 'mybookaro' ),
                    401
                );
            }
            // Anonymous booking: the payload's `name`/`email`/`phone` carry
            // identity; we just don't have a WP user_id to attach. Skip the
            // capability check (anonymous users have no caps to check) and
            // let the validators downstream police the rest.
        } elseif ( ! bookaro_user_has_customer_access() ) {
            return $this->error( 'bookaro_forbidden', __( 'Permission denied.', 'mybookaro' ), 403 );
        }

        $validation = apply_filters( 'bookaro_validate_booking_payload', null, $payload, $user_id );
        if ( is_wp_error( $validation ) ) {
            return $validation;
        }

        $listing_id = absint( $payload['listing_id'] ?? 0 );
        if ( $listing_id < 1 ) {
            $single_business = function_exists( 'bookaro_is_single_business_site' )
                && bookaro_is_single_business_site();
            if ( ! $single_business ) {
                return $this->error(
                    'bookaro_invalid_listing',
                    __( 'Listing is required for this booking flow.', 'mybookaro' ),
                    400
                );
            }
        } else {
            if ( function_exists( 'bookaro_is_allowed_listing' ) && ! bookaro_is_allowed_listing( $listing_id ) ) {
                return $this->error( 'bookaro_invalid_listing', __( 'Listing not supported.', 'mybookaro' ), 403 );
            }
            if ( function_exists( 'bookaro_is_lite_listing_allowed' ) && ! bookaro_is_lite_listing_allowed( $listing_id ) ) {
                return $this->error( 'bookaro_listing_not_allowed', __( 'Listing not supported.', 'mybookaro' ), 403 );
            }
        }

        $guard = $this->guard_booking_submission( $payload, $user_id );
        if ( is_wp_error( $guard ) ) {
            return $guard;
        }

        return $this->bookings->createBooking( $payload );
    }

    public function check_availability( int $provider_id, string $date, int $duration_minutes = 0 ): array
    {
        return $this->bookings->checkAvailability( $provider_id, $date, $duration_minutes );
    }

    public function get_provider_bookings( int $user_id )
    {
        if ( $user_id < 1 ) {
            return $this->error(
                'bookaro_auth_required',
                __( 'You must be logged in to view bookings.', 'mybookaro' ),
                401
            );
        }
        if ( ! bookaro_user_has_provider_access() ) {
            return $this->error(
                'bookaro_forbidden',
                __( 'You do not have permission to view provider bookings.', 'mybookaro' ),
                403
            );
        }

        return $this->bookings->getProviderBookings( $user_id );
    }

    public function get_owner_bookings( int $user_id )
    {
        if ( $user_id < 1 ) {
            return $this->error(
                'bookaro_auth_required',
                __( 'You must be logged in to view bookings.', 'mybookaro' ),
                401
            );
        }
        if ( ! bookaro_user_has_customer_access() ) {
            return $this->error(
                'bookaro_forbidden',
                __( 'You do not have permission to view customer bookings.', 'mybookaro' ),
                403
            );
        }

        return $this->bookings->getOwnerBookings( $user_id );
    }

    public function get_notification_logs( int $booking_id, int $user_id, bool $is_admin )
    {
        if ( $user_id < 1 ) {
            return $this->error(
                'bookaro_auth_required',
                __( 'You must be logged in to view notifications.', 'mybookaro' ),
                401
            );
        }
        if ( $booking_id < 1 ) {
            return $this->error( 'bookaro_invalid_booking', __( 'Invalid booking.', 'mybookaro' ), 400 );
        }
        // Bug 23: previously this gated on bookaro_user_has_provider_access()
        // and used $this->bookings->canManageBooking() (provider-only), which
        // blocked customers from seeing the notifications sent about their
        // OWN booking even though they could add a note / cancel it. Use the
        // per-booking actor role to permit customer + provider just like
        // /note and /cancel do.
        if ( ! $is_admin ) {
            $role = $this->get_booking_actor_role( $booking_id, $user_id );
            if ( null === $role || ! in_array( $role, [ 'customer', 'provider' ], true ) ) {
                return $this->error(
                    'bookaro_forbidden',
                    __( 'You are not allowed to view this booking.', 'mybookaro' ),
                    403
                );
            }
        }

        try {
            return $this->repo->get_notification_logs( $booking_id );
        } catch ( \Throwable $e ) {
            return $this->error(
                'bookaro_logs_failed',
                __( 'Failed to load notification logs.', 'mybookaro' ),
                500
            );
        }
    }

    public function cancel_booking( int $booking_id, int $user_id, string $reason = '' )
    {
        if ( $user_id < 1 ) {
            return $this->error(
                'bookaro_auth_required',
                __( 'You must be logged in to cancel a booking.', 'mybookaro' ),
                401
            );
        }
        if ( ! current_user_can( 'manage_options' )
            && ! bookaro_user_has_provider_access()
            && ! bookaro_user_has_customer_access()
        ) {
            return $this->error( 'bookaro_forbidden', __( 'Permission denied.', 'mybookaro' ), 403 );
        }
        if ( $booking_id < 1 ) {
            return $this->error( 'bookaro_invalid_booking', __( 'You cannot cancel this booking.', 'mybookaro' ), 400 );
        }

        $result = $this->bookings->cancelBooking( $booking_id, $user_id, $reason );
        if ( is_wp_error( $result ) ) {
            return $result;
        }
        if ( ! $result ) {
            return $this->error( 'bookaro_cancel_failed', __( 'You cannot cancel this booking.', 'mybookaro' ), 400 );
        }

        return $result;
    }

    public function add_note( int $booking_id, int $user_id, string $note )
    {
        if ( $user_id < 1 ) {
            return $this->error(
                'bookaro_auth_required',
                __( 'You must be logged in to add a note.', 'mybookaro' ),
                401
            );
        }
        if ( ! current_user_can( 'manage_options' )
            && ! bookaro_user_has_provider_access()
            && ! bookaro_user_has_customer_access()
        ) {
            return $this->error( 'bookaro_forbidden', __( 'Permission denied.', 'mybookaro' ), 403 );
        }
        if ( $booking_id < 1 ) {
            return $this->error( 'bookaro_invalid_booking', __( 'Invalid booking.', 'mybookaro' ), 400 );
        }

        $ok = $this->bookings->addNote( $booking_id, $user_id, $note );
        if ( ! $ok ) {
            return $this->error( 'bookaro_note_failed', __( 'Failed to save note.', 'mybookaro' ), 400 );
        }

        return true;
    }

    public function reschedule_booking( int $booking_id, int $user_id, array $payload )
    {
        if ( $user_id < 1 ) {
            return $this->error(
                'bookaro_auth_required',
                __( 'You must be logged in to reschedule a booking.', 'mybookaro' ),
                401
            );
        }
        if ( ! current_user_can( 'manage_options' )
            && ! bookaro_user_has_provider_access()
            && ! bookaro_user_has_customer_access()
        ) {
            return $this->error( 'bookaro_forbidden', __( 'Permission denied.', 'mybookaro' ), 403 );
        }
        if ( $booking_id < 1 ) {
            return $this->error( 'bookaro_invalid_booking', __( 'Invalid booking.', 'mybookaro' ), 400 );
        }

        $result = $this->bookings->rescheduleBooking( $booking_id, $user_id, $payload );
        if ( is_wp_error( $result ) ) {
            return $result;
        }
        if ( ! $result ) {
            return $this->error(
                'bookaro_reschedule_failed',
                __( 'Failed to reschedule booking.', 'mybookaro' ),
                400
            );
        }

        return $result;
    }

    public function update_status( int $booking_id, string $new_status, int $user_id, bool $is_admin )
    {
        if ( $user_id < 1 ) {
            return $this->error(
                'bookaro_auth_required',
                __( 'You are not allowed to update booking status.', 'mybookaro' ),
                401
            );
        }
        if ( ! $is_admin && ! bookaro_user_has_provider_access() ) {
            return $this->error(
                'bookaro_forbidden',
                __( 'You are not allowed to update booking status.', 'mybookaro' ),
                403
            );
        }
        if ( $booking_id < 1 ) {
            return $this->error( 'bookaro_invalid_booking', __( 'Invalid parameters.', 'mybookaro' ), 400 );
        }
        if ( ! $is_admin && $new_status === 'approved' && (bool) bookaro_get_option( 'auto_approve_bookings', 0 ) ) {
            return $this->error(
                'bookaro_auto_approve_enabled',
                __( 'Bookings are auto-approved and cannot be approved manually.', 'mybookaro' ),
                403
            );
        }
        $allowed = [ 'approved', 'rejected', 'completed', 'no_show' ];
        if ( ! in_array( $new_status, $allowed, true ) ) {
            return $this->error( 'bookaro_invalid_status', __( 'Invalid parameters.', 'mybookaro' ), 400 );
        }
        if ( ! $is_admin && ! $this->bookings->canManageBooking( $booking_id, $user_id ) ) {
            return $this->error(
                'bookaro_forbidden',
                __( 'You are not allowed to update this booking.', 'mybookaro' ),
                403
            );
        }

        $validation = $this->bookings->validateStatusChange( $booking_id, $new_status );
        if ( is_wp_error( $validation ) ) {
            return $validation;
        }
        $result = $this->bookings->updateBookingStatus( $booking_id, $new_status );
        if ( is_array( $result ) ) {
            return $result;
        }

        return true;
    }

    public function bulk_update_status( string $action, array $booking_ids, int $user_id, bool $is_admin )
    {
        if ( $user_id < 1 ) {
            return $this->error(
                'bookaro_auth_required',
                __( 'You are not allowed to update booking status.', 'mybookaro' ),
                401
            );
        }
        if ( ! $is_admin && ! bookaro_user_has_provider_access() ) {
            return $this->error(
                'bookaro_forbidden',
                __( 'You are not allowed to update booking status.', 'mybookaro' ),
                403
            );
        }
        if ( ! $is_admin && $action === 'approved' && (bool) bookaro_get_option( 'auto_approve_bookings', 0 ) ) {
            return $this->error(
                'bookaro_auto_approve_enabled',
                __( 'Bookings are auto-approved and cannot be approved manually.', 'mybookaro' ),
                403
            );
        }

        $allowed_actions = [ 'approved', 'rejected', 'cancelled' ];
        if ( empty( $booking_ids ) || ! in_array( $action, $allowed_actions, true ) ) {
            return $this->error( 'bookaro_invalid_params', __( 'Invalid parameters.', 'mybookaro' ), 400 );
        }

        $updated = [];
        $failed  = [];

        foreach ( $booking_ids as $booking_id ) {
            $booking_id = absint( $booking_id );
            if ( $booking_id < 1 ) {
                continue;
            }
            $booking = $this->repo->get_booking( $booking_id );
            if ( ! $booking ) {
                $failed[] = [
                    'id' => $booking_id,
                    'reason' => __( 'Booking not found.', 'mybookaro' ),
                ];
                continue;
            }
            if ( ! $is_admin && (int) $booking->provider_id !== (int) $user_id ) {
                $failed[] = [
                    'id' => $booking_id,
                    'reason' => __( 'You are not allowed to update this booking.', 'mybookaro' ),
                ];
                continue;
            }
            $current_status = (string) ( $booking->status ?? '' );

            if ( 'cancelled' === $action ) {
                if ( in_array( $current_status, [ 'cancelled', 'rejected', 'completed', 'no_show' ], true ) ) {
                    $failed[] = [
                        'id' => $booking_id,
                        'reason' => __( 'This booking cannot be cancelled.', 'mybookaro' ),
                    ];
                    continue;
                }
                // Bug 29: admin bulk-cancel needs to bypass cancelBooking's
                // "user must be customer or provider" check (admins aren't
                // either). Forward the $is_admin flag — non-admin actors
                // still hit the ownership check unchanged.
                $result = $this->bookings->cancelBooking( $booking_id, $user_id, '', $is_admin );
                if ( is_wp_error( $result ) ) {
                    $failed[] = [
                        'id' => $booking_id,
                        'reason' => $result->get_error_message(),
                    ];
                } elseif ( ! $result ) {
                    $failed[] = [
                        'id' => $booking_id,
                        'reason' => __( 'Failed to cancel booking.', 'mybookaro' ),
                    ];
                } else {
                    $updated[] = $booking_id;
                }
                continue;
            }

            if ( 'pending' !== $current_status ) {
                $failed[] = [
                    'id' => $booking_id,
                    'reason' => __( 'This booking cannot be updated.', 'mybookaro' ),
                ];
                continue;
            }
            $validation = $this->bookings->validateStatusChange( $booking_id, $action );
            if ( is_wp_error( $validation ) ) {
                $failed[] = [
                    'id' => $booking_id,
                    'reason' => $validation->get_error_message(),
                ];
                continue;
            }

            $this->bookings->updateBookingStatus( $booking_id, $action );
            $updated[] = $booking_id;
        }

        return [
            'action' => $action,
            'updated' => $updated,
            'failed' => $failed,
        ];
    }

    public function get_booking_actor_role( int $booking_id, int $user_id ): ?string
    {
        $booking_id = absint( $booking_id );
        $user_id = absint( $user_id );
        if ( $booking_id < 1 || $user_id < 1 ) {
            return null;
        }

        $booking = $this->repo->get_booking( $booking_id );
        if ( ! $booking ) {
            return null;
        }

        if ( (int) $booking->provider_id === $user_id ) {
            return 'provider';
        }
        if ( (int) $booking->customer_id === $user_id ) {
            return 'customer';
        }

        return '';
    }

    private function check_booking_throttle( array $payload, int $user_id ): bool|\WP_Error
    {
        if ( current_user_can( 'manage_options' ) ) {
            return true;
        }

        $listing_id = absint( $payload['listing_id'] ?? 0 );
        $ip = RateLimiter::client_ip();
        $identifier = RateLimiter::build_identifier( [
            'ip' => $ip,
            'user' => $user_id > 0 ? $user_id : 'guest',
        ] );

        $rate_config = apply_filters(
            'bookaro_rate_limit',
            [ 'limit' => 5, 'window' => 300 ],
            'booking_submit',
            [
                'user_id' => $user_id,
                'listing_id' => $listing_id,
                'ip' => $ip,
            ]
        );
        $limit = isset( $rate_config['limit'] ) ? (int) $rate_config['limit'] : 5;
        $window = isset( $rate_config['window'] ) ? (int) $rate_config['window'] : 300;

        $rate = RateLimiter::check( 'booking_submit', $identifier, $limit, $window );
        if ( ! $rate['allowed'] ) {
            $retry_after = max( 1, (int) $rate['retry_after'] );
            $message = sprintf(
                /* translators: %d: seconds to wait before retrying. */
                __( 'Too many booking requests. Please wait %d seconds and try again.', 'mybookaro' ),
                $retry_after
            );
            return new \WP_Error(
                'bookaro_rate_limited',
                $message,
                [
                    'status' => 429,
                    'retry_after' => $retry_after,
                ]
            );
        }

        return true;
    }

    private function guard_booking_submission( array $payload, int $user_id ): bool|\WP_Error
    {
        $throttle = $this->check_booking_throttle( $payload, $user_id );
        if ( is_wp_error( $throttle ) ) {
            return $throttle;
        }

        if ( current_user_can( 'manage_options' ) ) {
            return true;
        }

        $config = $this->get_spam_protection_config();
        $ip = RateLimiter::client_ip();
        $email = sanitize_email( (string) ( $payload['email'] ?? '' ) );
        $listing_id = absint( $payload['listing_id'] ?? 0 );
        $provider_id = absint( $payload['provider_id'] ?? 0 );
        $service_id = absint( $payload['service_id'] ?? 0 );
        $service_label = sanitize_text_field( (string) ( $payload['service_label'] ?? '' ) );
        $context = [
            'ip' => $ip,
            'email' => $email,
            'user_id' => $user_id,
            'listing_id' => $listing_id,
            'provider_id' => $provider_id,
            'service_id' => $service_id,
            'service_label' => $service_label,
            'booking_date' => sanitize_text_field( (string) ( $payload['booking_date'] ?? '' ) ),
            'booking_date_to' => sanitize_text_field( (string) ( $payload['booking_date_to'] ?? '' ) ),
        ];

        if ( ! empty( $config['honeypot_enabled'] ) ) {
            $honeypot = trim( sanitize_text_field( (string) ( $payload['spam_honeypot'] ?? '' ) ) );
            if ( $honeypot !== '' ) {
                return $this->build_spam_block_error( 'honeypot', $context, [
                    'status' => 400,
                    'message' => __( 'We could not process this booking request. Please try again.', 'mybookaro' ),
                ] );
            }
        }

        $min_fill_seconds = (int) ( $config['min_fill_seconds'] ?? 0 );
        if ( $min_fill_seconds > 0 ) {
            $elapsed = $this->resolve_booking_form_elapsed_seconds( $payload );
            if ( $elapsed >= 0 && $elapsed < $min_fill_seconds ) {
                $context['form_elapsed_seconds'] = $elapsed;
                $context['form_min_seconds'] = $min_fill_seconds;
                return $this->build_spam_block_error( 'too_fast', $context, [
                    'status' => 400,
                    'message' => __( 'We could not process this booking request. Please try again.', 'mybookaro' ),
                ] );
            }
        }

        $burst_window = max( 1, (int) ( $config['burst_window_seconds'] ?? 300 ) );
        $ip_limit = max( 0, (int) ( $config['burst_ip_limit'] ?? 0 ) );
        if ( $ip_limit > 0 ) {
            $ip_identifier = $ip !== '' ? $ip : 'unknown';
            $rate = RateLimiter::check( 'booking_submit_ip', $ip_identifier, $ip_limit, $burst_window );
            if ( ! $rate['allowed'] ) {
                $retry_after = max( 1, (int) $rate['retry_after'] );
                $context['retry_after'] = $retry_after;
                return $this->build_spam_block_error( 'burst_ip', $context, [
                    'status' => 429,
                    'retry_after' => $retry_after,
                    'message' => sprintf(
                        /* translators: %d: seconds to wait before retrying. */
                        __( 'Too many booking requests. Please wait %d seconds and try again.', 'mybookaro' ),
                        $retry_after
                    ),
                ] );
            }
        }

        $email_limit = max( 0, (int) ( $config['burst_email_limit'] ?? 0 ) );
        if ( $email_limit > 0 && $email !== '' ) {
            $email_identifier = strtolower( $email );
            $rate = RateLimiter::check( 'booking_submit_email', $email_identifier, $email_limit, $burst_window );
            if ( ! $rate['allowed'] ) {
                $retry_after = max( 1, (int) $rate['retry_after'] );
                $context['retry_after'] = $retry_after;
                return $this->build_spam_block_error( 'burst_email', $context, [
                    'status' => 429,
                    'retry_after' => $retry_after,
                    'message' => sprintf(
                        /* translators: %d: seconds to wait before retrying. */
                        __( 'Too many booking requests. Please wait %d seconds and try again.', 'mybookaro' ),
                        $retry_after
                    ),
                ] );
            }
        }

        $blacklist_words = $this->parse_blacklist_words( (string) ( $config['blacklist_words_raw'] ?? '' ) );
        if ( ! empty( $blacklist_words ) ) {
            $matched_word = $this->find_blacklisted_booking_word( $payload, $blacklist_words );
            if ( $matched_word !== '' ) {
                $context['matched_word'] = $matched_word;
                return $this->build_spam_block_error( 'blacklist', $context, [
                    'status' => 400,
                    'message' => __( 'We could not process this booking request. Please try again.', 'mybookaro' ),
                ] );
            }
        }

        $duplicate_hours = max( 0, (int) ( $config['duplicate_pending_hours'] ?? 0 ) );
        if ( $duplicate_hours > 0 ) {
            $duplicate = $this->repo->find_recent_pending_duplicate(
                absint( $user_id ),
                $provider_id,
                $listing_id,
                $service_id,
                $service_label,
                (string) ( $payload['booking_date'] ?? '' ),
                (string) ( $payload['booking_date_to'] ?? '' ),
                $duplicate_hours
            );
            if ( ! empty( $duplicate['id'] ) ) {
                $context['duplicate_booking_id'] = absint( $duplicate['id'] );
                $retry_after = $this->compute_duplicate_retry_after(
                    (string) ( $duplicate['created_at'] ?? '' ),
                    $duplicate_hours
                );
                if ( $retry_after > 0 ) {
                    $context['retry_after'] = $retry_after;
                }
                $error_args = [
                    'status' => 429,
                    'message' => __( 'A similar booking request is already pending. Please check your dashboard and try again later.', 'mybookaro' ),
                ];
                if ( $retry_after > 0 ) {
                    $error_args['retry_after'] = $retry_after;
                }
                return $this->build_spam_block_error( 'duplicate_pending', $context, $error_args );
            }
        }

        return true;
    }

    private function get_spam_protection_config(): array
    {
        return [
            'honeypot_enabled' => (bool) bookaro_get_option( 'booking_spam_honeypot_enabled', 1 ),
            'min_fill_seconds' => absint( bookaro_get_option( 'booking_spam_min_fill_seconds', 4 ) ),
            'burst_window_seconds' => absint( bookaro_get_option( 'booking_spam_burst_window_seconds', 300 ) ),
            'burst_ip_limit' => absint( bookaro_get_option( 'booking_spam_burst_ip_limit', 8 ) ),
            'burst_email_limit' => absint( bookaro_get_option( 'booking_spam_burst_email_limit', 5 ) ),
            'blacklist_words_raw' => (string) bookaro_get_option( 'booking_spam_blacklist_words', '' ),
            'duplicate_pending_hours' => absint( bookaro_get_option( 'booking_spam_duplicate_pending_hours', 24 ) ),
        ];
    }

    private function resolve_booking_form_elapsed_seconds( array $payload ): int
    {
        $elapsed = absint( $payload['spam_form_elapsed_seconds'] ?? 0 );
        if ( $elapsed > 0 ) {
            return $elapsed;
        }

        $started_at = absint( $payload['spam_form_started_at'] ?? 0 );
        if ( $started_at < 1 ) {
            return -1;
        }

        $now = time();
        if ( $started_at > $now ) {
            return 0;
        }

        return max( 0, $now - $started_at );
    }

    /**
     * @return array<int,string>
     */
    private function parse_blacklist_words( string $raw ): array
    {
        $raw = trim( $raw );
        if ( $raw === '' ) {
            return [];
        }

        $parts = preg_split( '/[\r\n,;]+/', $raw );
        if ( ! is_array( $parts ) ) {
            return [];
        }

        $words = [];
        foreach ( $parts as $part ) {
            $word = strtolower( trim( sanitize_text_field( (string) $part ) ) );
            if ( strlen( $word ) < 2 ) {
                continue;
            }
            $words[ $word ] = $word;
        }

        return array_values( $words );
    }

    private function find_blacklisted_booking_word( array $payload, array $blacklist_words ): string
    {
        $text = $this->collect_booking_text_for_blacklist( $payload );
        if ( $text === '' ) {
            return '';
        }

        foreach ( $blacklist_words as $word ) {
            if ( $word === '' ) {
                continue;
            }
            if ( stripos( $text, $word ) !== false ) {
                return $word;
            }
        }

        return '';
    }

    private function collect_booking_text_for_blacklist( array $payload ): string
    {
        $parts = [];
        foreach ( [ 'name', 'email', 'phone', 'notes', 'service_label' ] as $key ) {
            if ( empty( $payload[ $key ] ) || ! is_scalar( $payload[ $key ] ) ) {
                continue;
            }
            $parts[] = (string) $payload[ $key ];
        }

        if ( isset( $payload['booking_fields'] ) ) {
            $this->collect_scalar_values( $payload['booking_fields'], $parts );
        }

        return strtolower( trim( implode( "\n", $parts ) ) );
    }

    /**
     * @param mixed $value
     * @param array<int,string> $parts
     */
    private function collect_scalar_values( $value, array &$parts, int $depth = 0 ): void
    {
        if ( $depth > 3 ) {
            return;
        }
        if ( is_scalar( $value ) ) {
            $parts[] = (string) $value;
            return;
        }
        if ( ! is_array( $value ) ) {
            return;
        }
        foreach ( $value as $item ) {
            $this->collect_scalar_values( $item, $parts, $depth + 1 );
        }
    }

    private function compute_duplicate_retry_after( string $created_at, int $window_hours ): int
    {
        $created_at = trim( $created_at );
        $window_hours = max( 0, $window_hours );
        if ( $created_at === '' || $window_hours < 1 ) {
            return 0;
        }

        try {
            $created = new \DateTime( $created_at, wp_timezone() );
        } catch ( \Throwable $e ) {
            return 0;
        }

        $retry_after = ( $created->getTimestamp() + ( $window_hours * HOUR_IN_SECONDS ) ) - time();
        return max( 0, $retry_after );
    }

    private function build_spam_block_error( string $reason, array $context = [], array $args = [] ): \WP_Error
    {
        $reason = sanitize_key( $reason );
        if ( $reason === '' ) {
            $reason = 'blocked';
        }
        $status = isset( $args['status'] ) ? (int) $args['status'] : 400;
        if ( $status < 400 ) {
            $status = 400;
        }
        $message = isset( $args['message'] ) && is_string( $args['message'] ) && trim( $args['message'] ) !== ''
            ? $args['message']
            : __( 'We could not process this booking request. Please try again.', 'mybookaro' );
        $retry_after = isset( $args['retry_after'] ) ? absint( $args['retry_after'] ) : 0;

        $this->record_spam_block( $reason, $context );

        $error_data = [
            'status' => $status,
            'spam_reason' => $reason,
        ];
        if ( $retry_after > 0 ) {
            $error_data['retry_after'] = $retry_after;
        }

        return new \WP_Error( 'bookaro_spam_' . $reason, $message, $error_data );
    }

    private function record_spam_block( string $reason, array $context = [] ): void
    {
        $reason = sanitize_key( $reason );
        if ( $reason === '' ) {
            $reason = 'blocked';
        }

        $stats = get_option( 'bookaro_spam_stats', [] );
        if ( ! is_array( $stats ) ) {
            $stats = [];
        }
        $total_blocks = absint( $stats['total_blocks'] ?? 0 ) + 1;
        $reasons = $stats['reasons'] ?? [];
        if ( ! is_array( $reasons ) ) {
            $reasons = [];
        }
        $reasons[ $reason ] = absint( $reasons[ $reason ] ?? 0 ) + 1;
        $stats['total_blocks'] = $total_blocks;
        $stats['reasons'] = $reasons;
        $stats['last_blocked_at'] = time();
        $stats['last_reason'] = $reason;
        update_option( 'bookaro_spam_stats', $stats, false );

        $log_context = [
            'reason' => $reason,
            'ip' => sanitize_text_field( (string) ( $context['ip'] ?? '' ) ),
            'email' => sanitize_email( (string) ( $context['email'] ?? '' ) ),
            'user_id' => absint( $context['user_id'] ?? 0 ),
            'listing_id' => absint( $context['listing_id'] ?? 0 ),
            'provider_id' => absint( $context['provider_id'] ?? 0 ),
            'service_id' => absint( $context['service_id'] ?? 0 ),
            'booking_date' => sanitize_text_field( (string) ( $context['booking_date'] ?? '' ) ),
            'booking_date_to' => sanitize_text_field( (string) ( $context['booking_date_to'] ?? '' ) ),
            'retry_after' => absint( $context['retry_after'] ?? 0 ),
            'matched_word' => sanitize_text_field( (string) ( $context['matched_word'] ?? '' ) ),
            'duplicate_booking_id' => absint( $context['duplicate_booking_id'] ?? 0 ),
            'form_elapsed_seconds' => absint( $context['form_elapsed_seconds'] ?? 0 ),
            'form_min_seconds' => absint( $context['form_min_seconds'] ?? 0 ),
        ];
        $log_context = array_filter(
            $log_context,
            static function ( $value ): bool {
                if ( is_int( $value ) || is_float( $value ) ) {
                    return $value > 0;
                }
                return (string) $value !== '';
            }
        );

        $fingerprint = md5(
            $reason . '|' . (string) ( $log_context['ip'] ?? '' ) . '|' . (string) ( $log_context['email'] ?? '' )
        );
        $transient_key = 'bookaro_spam_log_' . $fingerprint;
        if ( get_transient( $transient_key ) ) {
            return;
        }
        set_transient( $transient_key, 1, 10 * MINUTE_IN_SECONDS );

        Logger::log( 'booking_spam_blocked_' . $reason, $log_context, false );
    }
}
