<?php
/**
 * Coupon Manager — Discount coupons for rental bookings
 *
 * Manages coupon CRUD, validation, application and usage tracking.
 * Supports Solo (admin-only) and Marketplace (admin + vendor) modes.
 *
 * Architecture:
 *   - Admin creates platform-wide coupons (vendor_id = 0)
 *   - Vendors can create their own coupons (Marketplace mode)
 *   - Coupons can be restricted to categories or specific products
 *   - Usage tracking per booking and per customer email
 *   - Integrates with WAHRB_Pricing_Engine for discount calculation
 *
 * @package WahraRentalsBookings
 * @since   2.3.0
 */

defined('ABSPATH') || exit;

/**
 * Class WAHRB_Coupon_Manager
 *
 * Singleton. Use WAHRB_Coupon_Manager::instance().
 */
class WAHRB_Coupon_Manager {

    /**
     * Singleton instance
     *
     * @var self|null
     */
    private static ?self $instance = null;

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

    /**
     * Get singleton instance
     *
     * @return self
     */
    public static function instance(): self {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Constructor
     */
    private function __construct() {
        $this->db = WAHRB_Database::instance();
    }

    // ──────────────────────────────────────────────────────────────
    // CRUD — Coupons
    // ──────────────────────────────────────────────────────────────

    /**
     * Create a coupon
     *
     * @param array $data Coupon data
     * @return int|false Coupon ID or false on failure
     */
    public function create_coupon(array $data) {
        $wpdb = $this->db->wpdb();
        $table = $this->db->table('coupons');

        $code = strtoupper(sanitize_text_field($data['code'] ?? ''));
        if (empty($code)) {
            return false;
        }

        // Check for unique code
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $exists = $wpdb->get_var($wpdb->prepare("SELECT coupon_id FROM {$table} WHERE code = %s", $code));
        if ($exists) {
            return false;
        }

        $insert = [
            'code'               => $code,
            'description'        => sanitize_textarea_field($data['description'] ?? ''),
            'discount_type'      => in_array($data['discount_type'] ?? '', ['percent', 'fixed'], true) ? $data['discount_type'] : 'percent',
            'discount_value'     => abs((float) ($data['discount_value'] ?? 0)),
            'min_order_amount'   => abs((float) ($data['min_order_amount'] ?? 0)),
            'max_discount_amount' => abs((float) ($data['max_discount_amount'] ?? 0)),
            'usage_limit'        => absint($data['usage_limit'] ?? 0),
            'usage_count'        => 0,
            'per_user_limit'     => absint($data['per_user_limit'] ?? 0),
            'valid_from'         => !empty($data['valid_from']) ? sanitize_text_field($data['valid_from']) : null,
            'valid_until'        => !empty($data['valid_until']) ? sanitize_text_field($data['valid_until']) : null,
            'category_slugs'     => !empty($data['category_slugs']) ? wp_json_encode(array_map('sanitize_text_field', (array) $data['category_slugs'])) : null,
            'product_ids'        => !empty($data['product_ids']) ? wp_json_encode(array_map('absint', (array) $data['product_ids'])) : null,
            'vendor_id'          => absint($data['vendor_id'] ?? 0),
            'created_by'         => get_current_user_id(),
            'is_active'          => !empty($data['is_active']) ? 1 : 0,
        ];

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->insert($table, $insert, [
            '%s', '%s', '%s', '%f', '%f', '%f', '%d', '%d', '%d',
            '%s', '%s', '%s', '%s', '%d', '%d', '%d',
        ]);

        return $result ? (int) $wpdb->insert_id : false;
    }

    /**
     * Update a coupon
     *
     * @param int   $coupon_id Coupon ID
     * @param array $data      Updated data
     * @return bool
     */
    public function update_coupon(int $coupon_id, array $data): bool {
        $wpdb = $this->db->wpdb();
        $table = $this->db->table('coupons');

        $update = [];
        $formats = [];

        if (isset($data['code'])) {
            $new_code = strtoupper(sanitize_text_field($data['code']));
            // Check uniqueness excluding self
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            $exists = $wpdb->get_var($wpdb->prepare("SELECT coupon_id FROM {$table} WHERE code = %s AND coupon_id != %d", $new_code, $coupon_id));
            if ($exists) {
                return false;
            }
            $update['code'] = $new_code;
            $formats[] = '%s';
        }

        $string_fields = ['description'];
        foreach ($string_fields as $f) {
            if (isset($data[$f])) {
                $update[$f] = sanitize_textarea_field($data[$f]);
                $formats[] = '%s';
            }
        }

        if (isset($data['discount_type'])) {
            $update['discount_type'] = in_array($data['discount_type'], ['percent', 'fixed'], true) ? $data['discount_type'] : 'percent';
            $formats[] = '%s';
        }

        $float_fields = ['discount_value', 'min_order_amount', 'max_discount_amount'];
        foreach ($float_fields as $f) {
            if (isset($data[$f])) {
                $update[$f] = abs((float) $data[$f]);
                $formats[] = '%f';
            }
        }

        $int_fields = ['usage_limit', 'per_user_limit'];
        foreach ($int_fields as $f) {
            if (isset($data[$f])) {
                $update[$f] = absint($data[$f]);
                $formats[] = '%d';
            }
        }

        if (isset($data['valid_from'])) {
            $update['valid_from'] = !empty($data['valid_from']) ? sanitize_text_field($data['valid_from']) : null;
            $formats[] = '%s';
        }
        if (isset($data['valid_until'])) {
            $update['valid_until'] = !empty($data['valid_until']) ? sanitize_text_field($data['valid_until']) : null;
            $formats[] = '%s';
        }

        if (isset($data['category_slugs'])) {
            $update['category_slugs'] = !empty($data['category_slugs']) ? wp_json_encode(array_map('sanitize_text_field', (array) $data['category_slugs'])) : null;
            $formats[] = '%s';
        }
        if (isset($data['product_ids'])) {
            $update['product_ids'] = !empty($data['product_ids']) ? wp_json_encode(array_map('absint', (array) $data['product_ids'])) : null;
            $formats[] = '%s';
        }

        if (isset($data['vendor_id'])) {
            $update['vendor_id'] = absint($data['vendor_id']);
            $formats[] = '%d';
        }

        if (isset($data['is_active'])) {
            $update['is_active'] = !empty($data['is_active']) ? 1 : 0;
            $formats[] = '%d';
        }

        if (empty($update)) {
            return false;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->update($table, $update, ['coupon_id' => $coupon_id], $formats, ['%d']);

        return $result !== false;
    }

    /**
     * Delete a coupon
     *
     * @param int $coupon_id Coupon ID
     * @return bool
     */
    public function delete_coupon(int $coupon_id): bool {
        $wpdb = $this->db->wpdb();

        // Delete usage records first
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->delete($this->db->table('coupon_usage'), ['coupon_id' => $coupon_id], ['%d']);

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $result = $wpdb->delete($this->db->table('coupons'), ['coupon_id' => $coupon_id], ['%d']);

        return $result !== false;
    }

    /**
     * Get a coupon by ID
     *
     * @param int $coupon_id Coupon ID
     * @return array|null
     */
    public function get_coupon(int $coupon_id): ?array {
        $wpdb = $this->db->wpdb();
        $table = $this->db->table('coupons');

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE coupon_id = %d", $coupon_id), ARRAY_A);

        return $row ? $this->prepare_coupon($row) : null;
    }

    /**
     * Get a coupon by code
     *
     * @param string $code Coupon code
     * @return array|null
     */
    public function get_coupon_by_code(string $code): ?array {
        $wpdb = $this->db->wpdb();
        $table = $this->db->table('coupons');

        $code = strtoupper(sanitize_text_field($code));

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE code = %s", $code), ARRAY_A);

        return $row ? $this->prepare_coupon($row) : null;
    }

    /**
     * Get all coupons (optionally filtered)
     *
     * @param array $args Filter arguments
     * @return array
     */
    public function get_coupons(array $args = []): array {
        $wpdb = $this->db->wpdb();
        $table = $this->db->table('coupons');

        $where = ['1=1'];
        $values = [];

        if (isset($args['vendor_id'])) {
            $where[] = 'vendor_id = %d';
            $values[] = absint($args['vendor_id']);
        }
        if (isset($args['is_active'])) {
            $where[] = 'is_active = %d';
            $values[] = $args['is_active'] ? 1 : 0;
        }
        if (isset($args['created_by'])) {
            $where[] = 'created_by = %d';
            $values[] = absint($args['created_by']);
        }

        $where_sql = implode(' AND ', $where);
        $sql = "SELECT * FROM {$table} WHERE {$where_sql} ORDER BY created_at DESC";

        if (!empty($values)) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
            $results = $wpdb->get_results($wpdb->prepare($sql, ...$values), ARRAY_A);
        } else {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
            $results = $wpdb->get_results($sql, ARRAY_A);
        }

        return array_map([$this, 'prepare_coupon'], $results ?: []);
    }

    /**
     * Prepare coupon data (decode JSON fields)
     *
     * @param array $row Raw DB row
     * @return array
     */
    private function prepare_coupon(array $row): array {
        $row['category_slugs'] = !empty($row['category_slugs']) ? json_decode($row['category_slugs'], true) : [];
        $row['product_ids'] = !empty($row['product_ids']) ? array_map('absint', json_decode($row['product_ids'], true) ?: []) : [];
        $row['discount_value'] = (float) $row['discount_value'];
        $row['min_order_amount'] = (float) $row['min_order_amount'];
        $row['max_discount_amount'] = (float) $row['max_discount_amount'];
        $row['usage_limit'] = (int) $row['usage_limit'];
        $row['usage_count'] = (int) $row['usage_count'];
        $row['per_user_limit'] = (int) $row['per_user_limit'];
        $row['vendor_id'] = (int) $row['vendor_id'];
        $row['is_active'] = (bool) $row['is_active'];
        return $row;
    }

    // ──────────────────────────────────────────────────────────────
    // Validation — Can a coupon be applied?
    // ──────────────────────────────────────────────────────────────

    /**
     * Validate a coupon code for a given booking context
     *
     * Returns ['valid' => bool, 'coupon' => [...], 'error' => string|null]
     *
     * @param string $code           Coupon code
     * @param int    $product_id     Product ID
     * @param float  $subtotal       Booking subtotal (before discount)
     * @param string $customer_email Customer email
     * @return array
     */
    public function validate_coupon(string $code, int $product_id, float $subtotal, string $customer_email = ''): array {
        $coupon = $this->get_coupon_by_code($code);

        if (!$coupon) {
            return [
                'valid' => false,
                'coupon' => null,
                'error' => __('Invalid coupon code.', 'wahra-rentals-bookings'),
            ];
        }

        // Check active
        if (!$coupon['is_active']) {
            return [
                'valid' => false,
                'coupon' => $coupon,
                'error' => __('This coupon is no longer active.', 'wahra-rentals-bookings'),
            ];
        }

        // Check date validity
        $now = current_time('mysql');
        if (!empty($coupon['valid_from']) && $now < $coupon['valid_from']) {
            return [
                'valid' => false,
                'coupon' => $coupon,
                'error' => __('This coupon is not yet valid.', 'wahra-rentals-bookings'),
            ];
        }
        if (!empty($coupon['valid_until']) && $now > $coupon['valid_until']) {
            return [
                'valid' => false,
                'coupon' => $coupon,
                'error' => __('This coupon has expired.', 'wahra-rentals-bookings'),
            ];
        }

        // Check global usage limit
        if ($coupon['usage_limit'] > 0 && $coupon['usage_count'] >= $coupon['usage_limit']) {
            return [
                'valid' => false,
                'coupon' => $coupon,
                'error' => __('This coupon has reached its usage limit.', 'wahra-rentals-bookings'),
            ];
        }

        // Check per-user limit
        if ($coupon['per_user_limit'] > 0 && !empty($customer_email)) {
            $user_usage = $this->get_user_usage_count((int) $coupon['coupon_id'], $customer_email);
            if ($user_usage >= $coupon['per_user_limit']) {
                return [
                    'valid' => false,
                    'coupon' => $coupon,
                    'error' => __('You have already used this coupon the maximum number of times.', 'wahra-rentals-bookings'),
                ];
            }
        }

        // Check minimum order amount
        if ($coupon['min_order_amount'] > 0 && $subtotal < $coupon['min_order_amount']) {
            return [
                'valid' => false,
                'coupon' => $coupon,
                /* translators: %s: minimum order amount */
                'error' => sprintf(__('Minimum order amount for this coupon is %s.', 'wahra-rentals-bookings'), wc_price($coupon['min_order_amount'])),
            ];
        }

        // Check category restriction
        if (!empty($coupon['category_slugs'])) {
            $product_category = get_post_meta($product_id, '_wahrb_platform_category', true) ?: '';
            if (!in_array($product_category, $coupon['category_slugs'], true)) {
                return [
                    'valid' => false,
                    'coupon' => $coupon,
                    'error' => __('This coupon is not valid for this product category.', 'wahra-rentals-bookings'),
                ];
            }
        }

        // Check product restriction
        if (!empty($coupon['product_ids'])) {
            if (!in_array($product_id, $coupon['product_ids'], true)) {
                return [
                    'valid' => false,
                    'coupon' => $coupon,
                    'error' => __('This coupon is not valid for this product.', 'wahra-rentals-bookings'),
                ];
            }
        }

        // Check vendor ownership — vendor coupons only work on that vendor's products
        if ($coupon['vendor_id'] > 0) {
            $product_vendor = (int) get_post_field('post_author', $product_id);
            if (wahrb_is_dokan_active() && function_exists('dokan_get_vendor_by_product')) {
                $vendor = dokan_get_vendor_by_product($product_id);
                if ($vendor && $vendor->get_id()) {
                    $product_vendor = $vendor->get_id();
                }
            }
            if ($product_vendor !== $coupon['vendor_id']) {
                return [
                    'valid' => false,
                    'coupon' => $coupon,
                    'error' => __('This coupon is not valid for this product.', 'wahra-rentals-bookings'),
                ];
            }
        }

        return [
            'valid' => true,
            'coupon' => $coupon,
            'error' => null,
        ];
    }

    // ──────────────────────────────────────────────────────────────
    // Application — Calculate and record discount
    // ──────────────────────────────────────────────────────────────

    /**
     * Calculate discount amount for a given coupon and subtotal
     *
     * @param array $coupon   Coupon data
     * @param float $subtotal Booking subtotal before discount
     * @return float Discount amount
     */
    public function calculate_discount(array $coupon, float $subtotal): float {
        if ($coupon['discount_type'] === 'percent') {
            $discount = round($subtotal * ($coupon['discount_value'] / 100), 2);
        } else {
            $discount = round($coupon['discount_value'], 2);
        }

        // Apply max discount cap if set
        if ($coupon['max_discount_amount'] > 0 && $discount > $coupon['max_discount_amount']) {
            $discount = $coupon['max_discount_amount'];
        }

        // Discount cannot exceed subtotal
        if ($discount > $subtotal) {
            $discount = $subtotal;
        }

        return $discount;
    }

    /**
     * Record coupon usage for a booking
     *
     * @param int    $coupon_id       Coupon ID
     * @param int    $booking_id      Booking ID
     * @param string $customer_email  Customer email
     * @param float  $discount_amount Discount applied
     * @return bool
     */
    public function record_usage(int $coupon_id, int $booking_id, string $customer_email, float $discount_amount): bool {
        $wpdb = $this->db->wpdb();

        // Insert usage record
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $inserted = $wpdb->insert(
            $this->db->table('coupon_usage'),
            [
                'coupon_id'       => $coupon_id,
                'booking_id'      => $booking_id,
                'customer_email'  => sanitize_email($customer_email),
                'discount_amount' => $discount_amount,
            ],
            ['%d', '%d', '%s', '%f']
        );

        if (!$inserted) {
            return false;
        }

        // Increment global usage count
        $table = $this->db->table('coupons');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $wpdb->query($wpdb->prepare("UPDATE {$table} SET usage_count = usage_count + 1 WHERE coupon_id = %d", $coupon_id));

        return true;
    }

    /**
     * Get usage count for a customer email on a specific coupon
     *
     * @param int    $coupon_id      Coupon ID
     * @param string $customer_email Customer email
     * @return int
     */
    public function get_user_usage_count(int $coupon_id, string $customer_email): int {
        $wpdb = $this->db->wpdb();
        $table = $this->db->table('coupon_usage');

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $count = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE coupon_id = %d AND customer_email = %s", $coupon_id, $customer_email));

        return (int) $count;
    }

    /**
     * Get usage records for a booking
     *
     * @param int $booking_id Booking ID
     * @return array
     */
    public function get_booking_usage(int $booking_id): array {
        $wpdb = $this->db->wpdb();
        $usage_table = $this->db->table('coupon_usage');
        $coupons_table = $this->db->table('coupons');

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $results = $wpdb->get_results($wpdb->prepare("SELECT u.*, c.code, c.discount_type, c.discount_value FROM {$usage_table} u LEFT JOIN {$coupons_table} c ON u.coupon_id = c.coupon_id WHERE u.booking_id = %d", $booking_id), ARRAY_A);

        return $results ?: [];
    }

    /**
     * Generate a random coupon code
     *
     * @param int    $length Code length
     * @param string $prefix Optional prefix
     * @return string
     */
    public static function generate_code(int $length = 8, string $prefix = ''): string {
        $chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Excludes confusing I/O/0/1
        $code = '';
        for ($i = 0; $i < $length; $i++) {
            $code .= $chars[wp_rand(0, strlen($chars) - 1)];
        }
        return $prefix ? strtoupper($prefix) . '-' . $code : $code;
    }
}
