<?php
/**
 * Hold system for anti double-booking
 *
 * @package WahraRentalsBookings
 */

defined('ABSPATH') || exit;

/**
 * Hold class
 */
class WAHRB_Hold {

    /**
     * Hold ID
     *
     * @var int
     */
    private int $id = 0;

    /**
     * Hold data
     *
     * @var array
     */
    private array $data = [];

    /**
     * Database instance
     *
     * @var WAHRB_Database
     */
    private WAHRB_Database $db;

    /**
     * Default hold duration in minutes
     *
     * @var int
     */
    private static int $default_duration = 30;

    /**
     * Constructor
     *
     * @param int|array $hold Hold ID or data
     */
    public function __construct($hold = 0) {
        $this->db = WAHRB_Database::instance();

        if (is_numeric($hold) && $hold > 0) {
            $this->id = (int) $hold;
            $this->read();
        } elseif (is_array($hold)) {
            $this->set_data($hold);
        }
    }

    /**
     * Read hold from database
     */
    private function read(): void {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $row = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$this->db->table('holds')} WHERE hold_id = %d",
            $this->id
        ), ARRAY_A);

        if ($row) {
            $this->set_data($row);
        }
    }

    /**
     * Set hold data
     *
     * @param array $data Hold data
     */
    private function set_data(array $data): void {
        $this->data = $data;
        if (isset($data['hold_id'])) {
            $this->id = (int) $data['hold_id'];
        }
    }

    /**
     * Get hold ID
     *
     * @return int
     */
    public function get_id(): int {
        return $this->id;
    }

    /**
     * Check if hold exists and is active
     *
     * @return bool
     */
    public function is_valid(): bool {
        if ($this->id <= 0 || empty($this->data)) {
            return false;
        }

        if (!$this->data['is_active']) {
            return false;
        }

        if (strtotime($this->data['expires_at']) < time()) {
            return false;
        }

        return true;
    }

    /**
     * Check if hold exists in the database
     *
     * @return bool
     */
    public function exists(): bool {
        return $this->id > 0 && !empty($this->data);
    }

    /**
     * Get a hold data field
     *
     * @param string $key Data key
     * @return mixed|null
     */
    public function get(string $key) {
        return $this->data[$key] ?? null;
    }

    /**
     * Get session token for current user
     *
     * @return string
     */
    private static function get_session_token(): string {
        // Use WooCommerce session instead of PHP native sessions (WordPress best practice)
        if (function_exists('WC') && WC()->session) {
            $token = WC()->session->get('wahrb_session_token');
            if (!$token) {
                $token = wp_generate_uuid4();
                WC()->session->set('wahrb_session_token', $token);
            }
            return sanitize_text_field($token);
        }

        // Fallback: use a cookie-based token
        if (isset($_COOKIE['wahrb_session_token'])) {
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
            return sanitize_text_field(wp_unslash($_COOKIE['wahrb_session_token']));
        }

        $token = wp_generate_uuid4();
        // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.cookies_setcookie
        setcookie('wahrb_session_token', $token, time() + 3600, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true);
        return $token;
    }

    /**
     * Create a new hold
     *
     * @param array $data Hold data
     * @return WAHRB_Hold|WP_Error
     */
    public static function create(array $data) {
        global $wpdb;
        $db = WAHRB_Database::instance();

        // Validate required fields
        if (empty($data['product_id']) || empty($data['start_dt']) || empty($data['end_dt'])) {
            return new WP_Error('missing_fields', __('Missing data', 'wahra-rentals-bookings'));
        }

        $session_token = $data['session_token'] ?? self::get_session_token();
        $product_id = absint($data['product_id']);
        $variation_id = !empty($data['variation_id']) ? absint($data['variation_id']) : null;

        // Check if hold already exists for this session/product
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $existing = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$db->table('holds')}
             WHERE session_token = %s AND product_id = %d AND is_active = 1",
            $session_token,
            $product_id
        ));

        if ($existing) {
            // Check availability for the new dates before updating (exclude current hold from count)
            $check_availability = WAHRB_Availability::check(
                $product_id,
                $data['start_dt'],
                $data['end_dt'],
                $variation_id,
                absint($data['qty'] ?? 1)
            );

            // The existing hold is already counted in availability, so if it shows available
            // OR if the existing hold covers the same dates, allow the update
            if (!$check_availability['available']) {
                // Check if the unavailability is only due to our own hold
                // by checking if available_units + our hold qty would suffice
                $our_qty = absint($existing->qty);
                $available_with_ours = ($check_availability['available_units'] ?? 0) + $our_qty;
                if ($available_with_ours < absint($data['qty'] ?? 1)) {
                    return new WP_Error('not_available', $check_availability['message']);
                }
            }

            // Update existing hold
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
            $wpdb->update(
                $db->table('holds'),
                [
                    'start_dt' => $data['start_dt'],
                    'end_dt' => $data['end_dt'],
                    'qty' => absint($data['qty'] ?? 1),
                    'expires_at' => gmdate('Y-m-d H:i:s', time() + (self::$default_duration * 60)),
                ],
                ['hold_id' => $existing->hold_id]
            );

            return new self($existing->hold_id);
        }

        // Check availability first
        $availability = WAHRB_Availability::check(
            $product_id,
            $data['start_dt'],
            $data['end_dt'],
            $variation_id,
            absint($data['qty'] ?? 1)
        );

        if (!$availability['available']) {
            return new WP_Error('not_available', $availability['message']);
        }

        // Get hold duration from settings
        $settings = get_option('wahrb_settings_booking', []);
        $duration_minutes = $settings['hold_duration_minutes'] ?? self::$default_duration;

        $insert_data = [
            'session_token' => $session_token,
            'product_id' => $product_id,
            'variation_id' => $variation_id,
            'start_dt' => $data['start_dt'],
            'end_dt' => $data['end_dt'],
            'qty' => absint($data['qty'] ?? 1),
            'customer_id' => get_current_user_id() ?: null,
            'customer_email' => !empty($data['customer_email']) ? sanitize_email($data['customer_email']) : null,
            'expires_at' => gmdate('Y-m-d H:i:s', time() + ($duration_minutes * 60)),
            'is_active' => 1,
            'converted_to_booking' => 0,
        ];

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $result = $wpdb->insert($db->table('holds'), $insert_data);

        if (!$result) {
            return new WP_Error('db_error', __('Error creating hold', 'wahra-rentals-bookings'));
        }

        $hold_id = $wpdb->insert_id;
        
        // Trigger hook for daily availability sync
        do_action('wahrb_hold_created', $hold_id, $insert_data);

        return new self($hold_id);
    }

    /**
     * Convert hold to booking
     *
     * @param int $booking_id Booking ID
     * @param int|null $order_id Order ID
     * @return bool
     */
    public function convert_to_booking(int $booking_id, ?int $order_id = null): bool {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $result = $wpdb->update(
            $this->db->table('holds'),
            [
                'converted_to_booking' => 1,
                'is_active' => 0,
                'booking_id' => $booking_id,
                'order_id' => $order_id,
            ],
            ['hold_id' => $this->id]
        );

        return $result !== false;
    }

    /**
     * Release hold
     *
     * @return bool
     */
    public function release(): bool {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $result = $wpdb->update(
            $this->db->table('holds'),
            ['is_active' => 0],
            ['hold_id' => $this->id]
        );

        return $result !== false;
    }

    /**
     * Release all holds for a booking
     *
     * @param int $booking_id Booking ID
     */
    public static function release_for_booking(int $booking_id): void {
        global $wpdb;
        $db = WAHRB_Database::instance();

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $wpdb->update(
            $db->table('holds'),
            ['is_active' => 0],
            ['booking_id' => $booking_id]
        );
    }

    /**
     * Cleanup expired holds (called via cron)
     */
    public static function cleanup_expired(): void {
        global $wpdb;
        $db = WAHRB_Database::instance();

        // Deactivate expired holds
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table, no user input
        $wpdb->query(
            "UPDATE {$db->table('holds')}
             SET is_active = 0
             WHERE is_active = 1 AND expires_at < NOW()"
        );

        // Delete very old holds (older than 7 days)
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table, no user input
        $wpdb->query(
            "DELETE FROM {$db->table('holds')}
             WHERE is_active = 0 AND created_at < DATE_SUB(NOW(), INTERVAL 7 DAY)"
        );
    }

    /**
     * Get hold by session token and product
     *
     * @param string $session_token Session token
     * @param int $product_id Product ID
     * @return WAHRB_Hold|null
     */
    public static function get_by_session(string $session_token, int $product_id): ?WAHRB_Hold {
        global $wpdb;
        $db = WAHRB_Database::instance();

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $hold = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$db->table('holds')}
             WHERE session_token = %s AND product_id = %d AND is_active = 1 AND expires_at > NOW()",
            $session_token,
            $product_id
        ), ARRAY_A);

        if ($hold) {
            return new self($hold);
        }

        return null;
    }

    /**
     * Extend hold expiration
     *
     * @param int $minutes Additional minutes
     * @return bool
     */
    public function extend(int $minutes = 15): bool {
        global $wpdb;

        $new_expiry = gmdate('Y-m-d H:i:s', strtotime($this->data['expires_at']) + ($minutes * 60));

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $result = $wpdb->update(
            $this->db->table('holds'),
            ['expires_at' => $new_expiry],
            ['hold_id' => $this->id]
        );

        if ($result !== false) {
            $this->data['expires_at'] = $new_expiry;
            return true;
        }

        return false;
    }

    /**
     * Get remaining seconds
     *
     * @return int
     */
    public function get_remaining_seconds(): int {
        if (!$this->is_valid()) {
            return 0;
        }

        return max(0, strtotime($this->data['expires_at']) - time());
    }
}
