<?php
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange
// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter
/**
 * Small durable queue for non-critical booking work.
 */
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class YATOON_Job_Queue {
    public static function init() {
        self::install_schema();
        add_filter( 'cron_schedules', array( __CLASS__, 'cron_schedules' ) );
        add_action( 'yatoon_process_jobs', array( __CLASS__, 'process' ) );
        add_action( 'yatoon_process_jobs_now', array( __CLASS__, 'process' ) );
        add_action( 'admin_post_yatoon_retry_job', array( __CLASS__, 'retry_job' ) );
        if ( ! wp_next_scheduled( 'yatoon_process_jobs' ) ) {
            wp_schedule_event( time() + 60, 'yatoon_minute', 'yatoon_process_jobs' );
        }
    }

    public static function cron_schedules( $schedules ) {
        $schedules['yatoon_minute'] = array( 'interval' => 60, 'display' => 'Every minute' );
        return $schedules;
    }

    public static function install_schema() {
        global $wpdb;
        if ( get_option( 'yatoon_job_queue_schema', '' ) === YATOON_DB_VERSION ) {
            return;
        }
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        $charset = $wpdb->get_charset_collate();
        dbDelta( "CREATE TABLE {$wpdb->prefix}yatoon_jobs (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            job_type varchar(60) NOT NULL,
            payload longtext NOT NULL,
            status varchar(20) DEFAULT 'pending',
            attempts int(11) DEFAULT 0,
            available_at datetime NOT NULL,
            locked_at datetime DEFAULT NULL,
            completed_at datetime DEFAULT NULL,
            last_error text DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY status_available (status,available_at),
            KEY job_type (job_type)
        ) {$charset};" );
        update_option( 'yatoon_job_queue_schema', YATOON_DB_VERSION, false );
    }

    public static function enqueue( $job_type, array $payload, $delay_seconds = 0 ) {
        global $wpdb;
        $inserted = $wpdb->insert(
            $wpdb->prefix . 'yatoon_jobs',
            array(
                'job_type' => sanitize_key( $job_type ),
                'payload' => wp_json_encode( $payload ),
                'status' => 'pending',
                'attempts' => 0,
                'available_at' => gmdate( 'Y-m-d H:i:s', time() + max( 0, absint( $delay_seconds ) ) ),
                'created_at' => current_time( 'mysql', true ),
                'updated_at' => current_time( 'mysql', true ),
            ),
            array( '%s', '%s', '%s', '%d', '%s', '%s', '%s' )
        );
        if ( $inserted && ! wp_next_scheduled( 'yatoon_process_jobs_now' ) ) {
            wp_schedule_single_event( time() + 5, 'yatoon_process_jobs_now' );
        }
        return $inserted ? (int) $wpdb->insert_id : 0;
    }

    public static function process( $limit = 10 ) {
        global $wpdb;
        $table = $wpdb->prefix . 'yatoon_jobs';
        $wpdb->query(
            "UPDATE {$table} SET status='retry', available_at=UTC_TIMESTAMP(), last_error='Recovered stale worker lock'
             WHERE status='running' AND locked_at < UTC_TIMESTAMP() - INTERVAL 15 MINUTE"
        );
        $wpdb->query(
            "DELETE FROM {$table} WHERE status='completed' AND completed_at < UTC_TIMESTAMP() - INTERVAL 30 DAY"
        );
        $jobs = $wpdb->get_results( $wpdb->prepare(
            "SELECT * FROM {$table}
             WHERE status IN ('pending','retry') AND available_at <= UTC_TIMESTAMP()
             ORDER BY id ASC LIMIT %d",
            max( 1, absint( $limit ) )
        ) );
        foreach ( $jobs as $job ) {
            $claimed = $wpdb->query( $wpdb->prepare(
                "UPDATE {$table} SET status='running', locked_at=UTC_TIMESTAMP(), attempts=attempts+1
                 WHERE id=%d AND status IN ('pending','retry')",
                $job->id
            ) );
            if ( ! $claimed ) {
                continue;
            }
            try {
                self::dispatch( $job->job_type, json_decode( $job->payload, true ) ?: array() );
                $wpdb->update(
                    $table,
                    array( 'status' => 'completed', 'completed_at' => current_time( 'mysql', true ), 'updated_at' => current_time( 'mysql', true ), 'last_error' => null ),
                    array( 'id' => $job->id ),
                    array( '%s', '%s', '%s', '%s' ),
                    array( '%d' )
                );
            } catch ( Throwable $error ) {
                $attempts = (int) $job->attempts + 1;
                $retry = $attempts < 4;
                $wpdb->update(
                    $table,
                    array(
                        'status' => $retry ? 'retry' : 'failed',
                        'available_at' => gmdate( 'Y-m-d H:i:s', time() + min( HOUR_IN_SECONDS, 60 * ( 2 ** $attempts ) ) ),
                        'last_error' => sanitize_textarea_field( $error->getMessage() ),
                        'updated_at' => current_time( 'mysql', true ),
                    ),
                    array( 'id' => $job->id ),
                    array( '%s', '%s', '%s', '%s' ),
                    array( '%d' )
                );
            }
        }
    }

    private static function dispatch( $job_type, array $payload ) {
        if ( 'booking_notifications' === $job_type ) {
            self::send_booking_notifications( absint( $payload['booking_id'] ?? 0 ) );
            return;
        }
        if ( 'workflow_action' === $job_type && class_exists( 'YATOON_Operations' ) ) {
            YATOON_Operations::execute_workflow_job( $payload );
            return;
        }
        if ( 'google_calendar_event' === $job_type ) {
            self::create_google_calendar_event( absint( $payload['booking_id'] ?? 0 ) );
            return;
        }
        do_action( 'yatoon_run_job_' . sanitize_key( $job_type ), $payload );
    }

    private static function send_booking_notifications( $booking_id ) {
        if ( is_callable( array( 'YATOON_Ajax', 'send_queued_booking_notifications' ) ) ) {
            YATOON_Ajax::send_queued_booking_notifications( $booking_id );
            return;
        }
        global $wpdb;
        $booking = $wpdb->get_row( $wpdb->prepare(
            "SELECT b.*, s.name service_name, s.duration_minutes, st.name staff_name
             FROM {$wpdb->prefix}yatoon_bookings b
             LEFT JOIN {$wpdb->prefix}yatoon_services s ON s.id=b.service_id
             LEFT JOIN {$wpdb->prefix}yatoon_staff st ON st.id=b.staff_id
             WHERE b.id=%d",
            $booking_id
        ) );
        if ( ! $booking ) {
            throw new RuntimeException( 'Booking no longer exists.' );
        }
        $business = get_option( 'yatoon_business_name', get_bloginfo( 'name' ) );
        $manage_url = ! empty( $booking->manage_token ) && class_exists( 'Yatoon_Manage_Booking' )
            ? Yatoon_Manage_Booking::get_manage_url( $booking->manage_token )
            : '';
        $subject = sprintf( 'Appointment confirmed - %s', $business );
        $message = sprintf(
            "Hi %s,\n\nYour appointment is confirmed.\n\nService: %s\nStaff: %s\nTime: %s\nTotal: $%s\n%s\n\n%s",
            $booking->customer_name,
            $booking->service_name ?: 'Service',
            $booking->staff_name ?: 'Any available staff',
            wp_date( 'F j, Y g:i A', strtotime( $booking->start_time ) ),
            number_format( (float) $booking->total_price, 2 ),
            $manage_url ? 'Manage appointment: ' . $manage_url : '',
            $business
        );
        if ( get_option( 'yatoon_send_confirmation_email', '1' ) === '1' && is_email( $booking->customer_email ) ) {
            wp_mail( $booking->customer_email, $subject, $message );
        }
        $admin_email = get_option( 'yatoon_contact_email', get_option( 'admin_email' ) );
        if ( get_option( 'yatoon_email_notify_admin', '1' ) === '1' && is_email( $admin_email ) ) {
            wp_mail( $admin_email, 'New booking #' . $booking_id, $message );
        }
        $booking_data = (array) $booking;
        $service = (object) array( 'name' => $booking->service_name, 'duration_minutes' => $booking->duration_minutes );
        $staff = (object) array( 'name' => $booking->staff_name );
        if ( class_exists( 'YATOON_Twilio' ) ) {
            YATOON_Twilio::send_customer_confirmation( $booking_data, $service, $staff );
            YATOON_Twilio::send_admin_alert( $booking_data, $service, $staff );
        }
        if ( class_exists( 'YATOON_PWA' ) ) {
            YATOON_PWA::send_push_to_admins( array(
                'title' => 'New Booking',
                'body' => $booking->customer_name . ' - ' . $booking->service_name,
                'url' => admin_url( 'admin.php?page=sbs-bookings' ),
                'booking_id' => $booking_id,
            ) );
        }
    }

    private static function create_google_calendar_event( $booking_id ) {
        if ( ! class_exists( 'YATOON_Google_Calendar' ) || ! YATOON_Google_Calendar::is_enabled() ) {
            return;
        }
        global $wpdb;
        $booking = $wpdb->get_row( $wpdb->prepare(
            "SELECT b.*, s.name service_name, s.duration_minutes, st.name staff_name
             FROM {$wpdb->prefix}yatoon_bookings b
             LEFT JOIN {$wpdb->prefix}yatoon_services s ON s.id=b.service_id
             LEFT JOIN {$wpdb->prefix}yatoon_staff st ON st.id=b.staff_id
             WHERE b.id=%d",
            $booking_id
        ), ARRAY_A );
        if ( ! $booking ) {
            throw new RuntimeException( 'Booking no longer exists.' );
        }
        $event_id = YATOON_Google_Calendar::add_event(
            $booking,
            (object) array(
                'name' => $booking['service_name'] ?? 'Service',
                'duration_minutes' => absint( $booking['duration_minutes'] ?? 0 ),
            ),
            (object) array( 'name' => $booking['staff_name'] ?? '' )
        );
        if ( ! $event_id ) {
            throw new RuntimeException( 'Google Calendar did not return an event ID.' );
        }
        $wpdb->update(
            $wpdb->prefix . 'yatoon_bookings',
            array( 'google_event_id' => sanitize_text_field( $event_id ) ),
            array( 'id' => $booking_id ),
            array( '%s' ),
            array( '%d' )
        );
    }

    public static function retry_job() {
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_die( 'Permission denied.' );
        }
        check_admin_referer( 'yatoon_retry_job' );
        global $wpdb;
        $wpdb->update(
            $wpdb->prefix . 'yatoon_jobs',
            array( 'status' => 'retry', 'available_at' => current_time( 'mysql', true ), 'last_error' => null ),
            array( 'id' => absint( $_GET['job_id'] ?? 0 ) ),
            array( '%s', '%s', '%s' ),
            array( '%d' )
        );
        wp_safe_redirect( admin_url( 'admin.php?page=yatoon-performance' ) );
        exit;
    }
}
