<?php

if (!defined('ABSPATH')) {
    die('You are not allowed to call this page directly.');
}

class MeprCouponsCtrl extends MeprCptCtrl
{
    /**
     * Load hooks for the coupons management.
     *
     * @return void
     */
    public function load_hooks()
    {
        add_filter('bulk_actions-edit-memberpresscoupon', 'MeprCouponsCtrl::disable_bulk');
        add_filter('post_row_actions', 'MeprCouponsCtrl::disable_row', 10, 2);
        add_action('admin_enqueue_scripts', 'MeprCouponsCtrl::admin_enqueue_scripts');
        add_action('manage_posts_custom_column', 'MeprCouponsCtrl::custom_columns', 10, 2);
        add_filter('manage_edit-memberpresscoupon_columns', 'MeprCouponsCtrl::columns');
        add_action('admin_init', 'MeprCoupon::expire_old_coupons_and_cleanup_db');
        add_action('save_post', 'MeprCouponsCtrl::save_postdata');
        add_action('wp_insert_post_data', 'MeprCouponsCtrl::sanitize_and_validate_coupon', 99, 2);
        add_filter('default_title', 'MeprCouponsCtrl::get_page_title_code');
        add_action('mepr_txn_store', 'MeprCouponsCtrl::update_coupon_usage_count');
        add_action('mepr_subscr_store', 'MeprCouponsCtrl::update_coupon_usage_count');

        // Cleanup list view.
        add_filter('views_edit-' . MeprCoupon::$cpt, 'MeprAppCtrl::cleanup_list_view');

        // Ajax coupon validation.
        add_action('wp_ajax_mepr_validate_coupon', 'MeprCouponsCtrl::validate_coupon_ajax');
        add_action('wp_ajax_nopriv_mepr_validate_coupon', 'MeprCouponsCtrl::validate_coupon_ajax');

        // Admin notice for coupon validation errors.
        add_action('admin_notices', 'MeprCouponsCtrl::display_coupon_validation_notice');
    }

    /**
     * Register the custom post type for coupons.
     *
     * @return void
     */
    public function register_post_type()
    {
        register_post_type(MeprCoupon::$cpt, [
            'labels'               => [
                'name'               => __('Coupons', 'memberpress'),
                'singular_name'      => __('Coupon', 'memberpress'),
                'add_new'            => __('Add New', 'memberpress'),
                'add_new_item'       => __('Add New Coupon', 'memberpress'),
                'edit_item'          => __('Edit Coupon', 'memberpress'),
                'new_item'           => __('New Coupon', 'memberpress'),
                'view_item'          => __('View Coupon', 'memberpress'),
                'search_items'       => __('Search Coupons', 'memberpress'),
                'not_found'          => __('No Coupons found', 'memberpress'),
                'not_found_in_trash' => __('No Coupons found in Trash', 'memberpress'),
                'parent_item_colon'  => __('Parent Coupon:', 'memberpress'),
            ],
            'public'               => false,
            'show_ui'              => true,
            'show_in_menu'         => 'memberpress',
            'capability_type'      => 'page',
            'hierarchical'         => false,
            'register_meta_box_cb' => 'MeprCouponsCtrl::add_meta_boxes',
            'rewrite'              => false,
            'supports'             => ['title'],
        ]);
    }

    /**
     * Define the columns for the coupon list table.
     *
     * @param  array $columns The existing columns.
     * @return array
     */
    public static function columns($columns)
    {
        $columns = [
            'cb'                 => '<input type="checkbox" />',
            'title'              => __('Code', 'memberpress'),
            'coupon-description' => __('Description', 'memberpress'),
            'date'               => __('Created', 'memberpress'),
            'coupon-discount'    => __('Discount', 'memberpress'),
            'coupon-dm'          => __('Mode', 'memberpress'),
            'coupon-starts'      => __('Starts', 'memberpress'),
            'coupon-expires'     => __('Expires', 'memberpress'),
            'coupon-count'       => __('Usage Count', 'memberpress'),
            'coupon-products'    => __('Applies To', 'memberpress'),
        ];

        return $columns;
    }

    /**
     * Render custom columns in the coupon list table.
     *
     * @param  string  $column    The name of the column.
     * @param  integer $coupon_id The ID of the coupon.
     * @return void
     */
    public static function custom_columns($column, $coupon_id)
    {
        $mepr_options = MeprOptions::fetch();
        $coupon       = new MeprCoupon($coupon_id);

        if ($coupon->ID !== null) {
            switch ($column) {
                case 'coupon-description':
                    echo esc_html(wp_strip_all_tags($coupon->post_content));
                    break;
                case 'coupon-discount':
                    if ($coupon->is_membership_specific) {
                        echo esc_html__('Membership Specific', 'memberpress');
                        break;
                    }
                    if ($coupon->discount_mode === 'first-payment') {
                        echo esc_html($coupon->first_payment_discount_amount); // Update this to show proper currency symbol later.
                        echo esc_html(($coupon->first_payment_discount_type === 'percent') ? __('%', 'memberpress') : $mepr_options->currency_code);
                        echo ' → ';
                    }

                    echo esc_html($coupon->discount_amount); // Update this to show proper currency symbol later.
                    echo esc_html(($coupon->discount_type === 'percent') ? __('%', 'memberpress') : $mepr_options->currency_code);
                    break;
                case 'coupon-starts':
                    if ($coupon->post_status !== 'trash') {
                        if ($coupon->should_start) {
                                echo esc_html(MeprUtils::get_date_from_ts($coupon->starts_on));
                        } else {
                              esc_html_e('Immediately', 'memberpress');
                        }
                    } else {
                        esc_html_e('Expired', 'memberpress'); // They've moved this to trash so show it as expired.
                    }
                    break;
                case 'coupon-expires':
                    if ($coupon->post_status !== 'trash') {
                        if ($coupon->should_expire) {
                                echo esc_html(MeprUtils::get_date_from_ts($coupon->expires_on));
                        } else {
                              esc_html_e('Never', 'memberpress');
                        }
                    } else {
                        esc_html_e('Expired', 'memberpress'); // They've moved this to trash so show it as expired.
                    }
                    break;
                case 'coupon-count':
                    echo '<a href="' . esc_url(admin_url('admin.php?page=memberpress-trans&coupon_id=' . $coupon->ID)) . '">';
                    if ($coupon->usage_amount) {
                        echo esc_html((int)$coupon->usage_count . ' / ' . $coupon->usage_amount);
                    } else {
                        echo esc_html((int)$coupon->usage_count . ' / ' . __('Unlimited', 'memberpress'));
                    }
                    echo '</a>';
                    break;
                case 'coupon-dm':
                    if ($coupon->discount_mode === 'trial-override') {
                        echo esc_html(
                            sprintf(
                                // Translators: %1$s: trial days, %2$s: trial amount.
                                __('Trial: %1$s days for %2$s', 'memberpress'),
                                $coupon->trial_days,
                                MeprAppHelper::format_currency($coupon->trial_amount)
                            )
                        );
                    } elseif ($coupon->discount_mode === 'first-payment') {
                        echo esc_html_x('First Payment', 'ui', 'memberpress');
                    } elseif ($coupon->discount_mode === 'standard') {
                        echo esc_html_x('Standard', 'ui', 'memberpress');
                    } else {
                        echo esc_html_x('None', 'ui', 'memberpress');
                    }
                    break;
                case 'coupon-products':
                    echo esc_html(implode(', ', $coupon->get_formatted_products()));
            }
        }
    }

    /**
     * Add meta boxes for the coupon edit screen.
     *
     * @return void
     */
    public static function add_meta_boxes()
    {
        global $post_id;
        $c = new MeprCoupon($post_id);

        add_meta_box('memberpress-coupon-meta', __('Coupon Options', 'memberpress'), 'MeprCouponsCtrl::coupon_meta_box', MeprCoupon::$cpt, 'normal', 'high');
        add_meta_box('memberpress-coupon-description', __('Description', 'memberpress'), 'MeprCouponsCtrl::coupon_description_box', MeprCoupon::$cpt, 'normal', 'high');

        MeprHooks::do_action('mepr_coupon_meta_boxes', $c);
    }

    /**
     * Display the coupon meta box.
     *
     * @return void
     */
    public static function coupon_meta_box()
    {
        global $post_id;
        $mepr_options = MeprOptions::fetch();
        $c            = new MeprCoupon($post_id);

        MeprView::render('/admin/coupons/form', get_defined_vars());
    }

    /**
     * Display the coupon description box.
     *
     * @return void
     */
    public static function coupon_description_box()
    {
        global $post_id;
        $c = new MeprCoupon($post_id);

        ?>
    <textarea name="content" id="excerpt"><?php echo esc_textarea($c->post_content); ?></textarea>
        <?php
    }

    /**
     * Save the coupon's metadata when the post is saved.
     *
     * @param  integer $post_id The ID of the post being saved.
     * @return mixed
     */
    public static function save_postdata($post_id)
    {
        $post = get_post($post_id);

        if (!wp_verify_nonce((isset($_POST[MeprCoupon::$nonce_str])) ? sanitize_text_field(wp_unslash($_POST[MeprCoupon::$nonce_str])) : '', MeprCoupon::$nonce_str . wp_salt())) {
            return $post_id; // Nonce prevents meta data from being wiped on move to trash.
        }

        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return $post_id;
        }

        if (defined('DOING_AJAX')) {
            return;
        }

        if (!empty($post) && $post->post_type === MeprCoupon::$cpt) {
            $coupon = new MeprCoupon($post_id);
            self::prepare_dates_data_for_db($coupon);

            if (isset($_POST[MeprCoupon::$usage_amount_str]) and is_numeric($_POST[MeprCoupon::$usage_amount_str])) {
                $coupon->usage_amount = sanitize_text_field(wp_unslash($_POST[MeprCoupon::$usage_amount_str]));
            } else {
                $coupon->usage_amount = 0;
            }

            $coupon->is_membership_specific = isset($_POST[MeprCoupon::$is_membership_specific_str]);
            $discount_settings              = self::parse_discount_settings(
                $coupon->is_membership_specific ?
                [] : // If is membership specific, we want to reset the general coupon discount settings.
                $_POST
            );

            // Set the general coupon discount settings.
            foreach ($discount_settings as $key => $value) {
                $coupon->$key = $value;
            }

            // Reset membership_specific.
            $coupon->membership_specific = [];

            // If is membership specific, we want to parse the discount settings for each membership.
            if ($coupon->is_membership_specific) {
                $membership_specific = [];
                foreach ($_POST[MeprCoupon::$membership_specific_str] ?? [] as $membership_id => $settings) {
                    $membership_specific[$membership_id] = self::parse_discount_settings($settings);
                }
                $coupon->membership_specific = $membership_specific;
            }

            $use_on_upgrades_downgrades         = sanitize_text_field(
                wp_unslash($_POST[MeprCoupon::$use_on_upgrades_downgrades_str] ?? 'none')
            );
            $coupon->use_on_upgrades_downgrades = array_key_exists(
                $use_on_upgrades_downgrades,
                MeprCouponsHelper::get_usage_on_upgrades_downgrades_types()
            ) ?
                $use_on_upgrades_downgrades
                : 'none';

            $coupon->use_on_ud_if_already_used = (bool) (wp_unslash($_POST[MeprCoupon::$use_on_ud_if_already_used_str] ?? false));

            // Handle use on upgrades and downgrades dependencies.
            $use_if_other_coupon_used         = sanitize_text_field(
                wp_unslash($_POST[MeprCoupon::$use_if_other_coupon_used_str] ?? 'unset')
            );
            $coupon->use_if_other_coupon_used = array_key_exists(
                $use_if_other_coupon_used,
                MeprCouponsHelper::get_usage_if_other_coupon_used_types()
            ) ?
                $use_if_other_coupon_used
                : 'unset';

            $coupon->use_if_specific_coupons = [];
            if (in_array($coupon->use_if_other_coupon_used, ['anyof', 'noneof', 'noneoranyof'], true)) {
                $coupon->use_if_specific_coupons = isset($_POST[MeprCoupon::$use_if_specific_coupons_str]) ?
                    array_map('absint', wp_unslash($_POST[MeprCoupon::$use_if_specific_coupons_str])) :
                    [];
            }

            $coupon->valid_products = isset($_POST[MeprCoupon::$valid_products_str]) ? array_map('sanitize_text_field', wp_unslash($_POST[MeprCoupon::$valid_products_str])) : [];

            if (isset($_POST[MeprCoupon::$usage_per_user_count_str]) and is_numeric($_POST[MeprCoupon::$usage_per_user_count_str])) {
                $coupon->usage_per_user_count = $coupon->usage_amount <= 0 || $_POST[MeprCoupon::$usage_per_user_count_str] <= $coupon->usage_amount ? sanitize_text_field(wp_unslash($_POST[MeprCoupon::$usage_per_user_count_str])) : $coupon->usage_amount;
            } else {
                $coupon->usage_per_user_count = 0;
            }
            $coupon->usage_per_user_count_timeframe = isset($_POST[MeprCoupon::$usage_per_user_count_timeframe_str]) ? sanitize_text_field(wp_unslash($_POST[MeprCoupon::$usage_per_user_count_timeframe_str])) : 'lifetime';
            $coupon->store_meta();

            MeprHooks::do_action('mepr_coupon_save_meta', $coupon);
        }
    }

    /**
     * Prepare dates data for database storage.
     *
     * @param  MeprCoupon $coupon            The coupon object.
     * @param  array      $coupon_dates_data Optional. The dates data array from process_date_fields. If empty, will be retrieved from $_POST.
     * @return void
     */
    private static function prepare_dates_data_for_db(MeprCoupon $coupon, array $coupon_dates_data = []): void
    {
        if (empty($coupon_dates_data)) {
            $coupon_dates_data = MeprCouponsHelper::process_date_fields($_POST);
        }

        $coupon->should_start    = $coupon_dates_data['should_start'];
        $coupon->starts_on       = $coupon_dates_data['start_date_ts'];
        $coupon->start_timezone  = $coupon_dates_data['start_timezone'];
        $coupon->should_expire   = $coupon_dates_data['should_end'];
        $coupon->expires_on      = $coupon_dates_data['end_date_ts'];
        $coupon->expire_timezone = $coupon_dates_data['end_timezone'];
    }

    /**
     * Parse discount settings.
     *
     * @param  array $data_source The data source.
     * @return array
     */
    private static function parse_discount_settings(array $data_source): array
    {
        // Default values.
        $to_return = [
            'discount_mode'                 => 'standard',
            'discount_type'                 => 'percent',
            'discount_amount'               => 0,
            'first_payment_discount_type'   => 'percent',
            'first_payment_discount_amount' => 0,
            'trial_days'                    => 0,
            'trial_amount'                  => 0.00,
        ];

        $to_return['discount_mode'] = isset($data_source[MeprCoupon::$discount_mode_str]) ?
            sanitize_text_field(wp_unslash($data_source[MeprCoupon::$discount_mode_str])) :
            $to_return['discount_mode'];

        $to_return['discount_type'] = isset($data_source[MeprCoupon::$discount_type_str]) ?
            sanitize_text_field(wp_unslash($data_source[MeprCoupon::$discount_type_str])) :
            $to_return['discount_type'];

        $to_return['discount_amount'] = isset($data_source[MeprCoupon::$discount_amount_str]) ?
            (float)sanitize_text_field(wp_unslash($data_source[MeprCoupon::$discount_amount_str])) : 0;

        if ($to_return['discount_type'] === 'percent' && $to_return['discount_amount'] > 100) {
            $to_return['discount_amount'] = 100; // Make sure percent is never > 100.
        }
        switch ($to_return['discount_mode']) {
            case 'first-payment':
                $to_return['first_payment_discount_type']   = isset($data_source[MeprCoupon::$first_payment_discount_type_str]) ?
                    sanitize_text_field(wp_unslash($data_source[MeprCoupon::$first_payment_discount_type_str])) :
                    $to_return['first_payment_discount_type'];
                $to_return['first_payment_discount_amount'] = isset($data_source[MeprCoupon::$first_payment_discount_amount_str]) ?
                    (float)sanitize_text_field(wp_unslash($data_source[MeprCoupon::$first_payment_discount_amount_str])) :
                    $to_return['first_payment_discount_amount'];

                if (
                    $to_return['first_payment_discount_type'] === 'percent' &&
                    $to_return['first_payment_discount_amount'] > 100
                ) {
                    $to_return['first_payment_discount_amount'] = 100; // Make sure percent is never > 100.
                }

                break;

            case 'trial-override':
                $to_return['trial_days']   = isset($data_source[MeprCoupon::$trial_days_str]) ?
                    (int)sanitize_text_field(wp_unslash($data_source[MeprCoupon::$trial_days_str])) :
                    $to_return['trial_days'];
                $to_return['trial_amount'] = isset($data_source[MeprCoupon::$trial_amount_str]) ?
                    (float) sanitize_text_field(wp_unslash($data_source[MeprCoupon::$trial_amount_str])) :
                    $to_return['trial_amount'];

                break;

            default:
                break;
        }

        return $to_return;
    }

    /**
     * Sanitize and validate coupon data before saving.
     *
     * Handles title sanitization, duplicate title prevention,
     * and validates that at least one membership is selected.
     *
     * @param  array $data    The post data.
     * @param  array $postarr The post array.
     * @return array
     */
    public static function sanitize_and_validate_coupon($data, $postarr)
    {
        global $wpdb;

        if ($data['post_type'] === MeprCoupon::$cpt) {
            // Get rid of invalid chars.
            $data['post_title'] = preg_replace(['/ +/', '/[^A-Za-z0-9_-]/'], ['-', ''], $data['post_title']);

            // Duplicate titles handling.
            $q1    = "SELECT ID FROM {$wpdb->posts} WHERE post_title = %s AND post_type = %s AND ID <> %d LIMIT 1";
            $q2    = $wpdb->prepare($q1, $data['post_title'], MeprCoupon::$cpt, $postarr['ID']); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $count = 0;

            if (is_admin()) {
                // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery
                while ($wpdb->get_var($q2)) {
                    ++$count; // Want to increment before running the query, so when we exit the loop $data['post_title'] . "-{$count}" is stil valid.
                    $q2 = $wpdb->prepare($q1, $data['post_title'] . "-{$count}", MeprCoupon::$cpt, $postarr['ID']); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
                }
            }

            if ($count > 0) {
                $data['post_title'] .= "-{$count}";
            }

            // Validate that at least one membership is selected.
            // Only check when trying to publish and the nonce is present (actual form submission).
            $nonce_value = isset($_POST[MeprCoupon::$nonce_str]) ? sanitize_text_field(wp_unslash($_POST[MeprCoupon::$nonce_str])) : '';
            if (wp_verify_nonce($nonce_value, MeprCoupon::$nonce_str . wp_salt())) {
                $valid_products = isset($_POST[MeprCoupon::$valid_products_str]) ? array_filter($_POST[MeprCoupon::$valid_products_str]) : [];

                if (empty($valid_products) && in_array($data['post_status'], ['publish', 'pending', 'future'], true)) {
                    // Force the coupon to draft status if no memberships are selected.
                    $data['post_status'] = 'draft';

                    // Set a transient to display an admin notice.
                    set_transient('mepr_coupon_no_products_error_' . get_current_user_id(), true, 30);
                }
            }
        }

        return $data;
    }

    /**
     * Display admin notice when coupon is saved without memberships.
     *
     * @return void
     */
    public static function display_coupon_validation_notice()
    {
        $transient_key = 'mepr_coupon_no_products_error_' . get_current_user_id();

        if (get_transient($transient_key)) {
            delete_transient($transient_key);
            ?>
            <div class="notice notice-error is-dismissible">
                <p><strong><?php esc_html_e('ERROR', 'memberpress'); ?></strong>: <?php esc_html_e('Please select at least one Membership before publishing the coupon. The coupon has been saved as a draft.', 'memberpress'); ?></p>
            </div>
            <?php
        }
    }

    /**
     * Disable certain row actions for the coupon list table.
     *
     * @param  array   $actions The existing actions.
     * @param  WP_Post $post    The current post object.
     * @return array
     */
    public static function disable_row($actions, $post)
    {
        global $current_screen;

        if (!isset($current_screen->post_type) || $current_screen->post_type !== MeprCoupon::$cpt) {
            return $actions;
        }

        unset($actions['inline hide-if-no-js']); // Hides quick-edit.
        unset($actions['delete']); // Hides permanantely delete.

        return $actions;
    }

    /**
     * Disable certain bulk actions for the coupon list table.
     *
     * @param  array $actions The existing actions.
     * @return array
     */
    public static function disable_bulk($actions)
    {
        unset($actions['delete']); // Disables permanent delete bulk action.
        unset($actions['edit']); // Disables bulk edit.

        return $actions;
    }

    /**
     * Enqueue scripts and styles for the coupon admin page.
     *
     * @param  string $hook The current admin page hook.
     * @return void
     */
    public static function admin_enqueue_scripts($hook)
    {
        global $current_screen;

        $l10n = ['mepr_no_products_message' => __('Please select at least one Membership before saving.', 'memberpress')];

        if ($current_screen->post_type === MeprCoupon::$cpt) {
            $coupon = new MeprCoupon(get_the_ID());
            if ($coupon && $coupon->is_membership_specific) {
                $membership_specific = $coupon->membership_specific;
            }

            wp_register_style('mepr-settings-table-css', MEPR_CSS_URL . '/settings_table.css', [], MEPR_VERSION);
            wp_enqueue_style('mepr-coupons-css', MEPR_CSS_URL . '/admin-coupons.css', ['mepr-settings-table-css'], MEPR_VERSION);

            wp_register_script('mepr-settings-table-js', MEPR_JS_URL . '/settings_table.js', ['jquery'], MEPR_VERSION);
            wp_dequeue_script('autosave'); // Disable auto-saving.
            wp_enqueue_script('mepr-coupons-js', MEPR_JS_URL . '/admin_coupons.js', ['jquery','mepr-settings-table-js'], MEPR_VERSION);
            wp_localize_script(
                'mepr-coupons-js',
                'MeprCoupon',
                [
                    'l10n' => $l10n,
                    'membership_specific' => $membership_specific ?? [],
                ]
            );

            MeprHooks::do_action('mepr_coupon_admin_enqueue_script', $hook);
        }
    }

    /**
     * Generate a random title code for the coupon page.
     *
     * @param  string $title The current title.
     * @return string
     */
    public static function get_page_title_code($title)
    {
        global $current_screen;

        if (empty($title) && isset($current_screen->post_type) && $current_screen->post_type === MeprCoupon::$cpt) {
            return MeprUtils::random_string(10, false, true);
        } else {
            return $title;
        }
    }

    /**
     * Validate the coupon via AJAX.
     *
     * @param  string|null  $code       The coupon code.
     * @param  integer|null $product_id The product ID.
     * @return void
     */
    public static function validate_coupon_ajax($code = null, $product_id = null)
    {
        check_ajax_referer('mepr_coupons', 'coupon_nonce');

        if (empty($code) || empty($product_id)) {
            if (!isset($_POST['code']) || empty($_POST['code']) || !isset($_POST['prd_id']) || empty($_POST['prd_id'])) {
                echo 'false';
                die();
            } else {
                $code       = sanitize_text_field(wp_unslash($_POST['code']));
                $product_id = sanitize_text_field(wp_unslash($_POST['prd_id']));
            }
        }

        $is_valid = MeprCoupon::is_valid_coupon_code($code, $product_id);
        $output   = MeprHooks::apply_filters('mepr_validate_coupon', $is_valid, $code, $product_id);

        if ($output) {
            echo 'true';
        } else {
            echo 'false';
        }

        die();
    }

    /**
     * Update the usage count of a coupon.
     *
     * @param  object $object The object containing the coupon ID.
     * @return void
     */
    public static function update_coupon_usage_count($object)
    {
        if (!isset($object->coupon_id) || empty($object->coupon_id)) {
            return;
        }

        $coupon = new MeprCoupon($object->coupon_id);

        if ($coupon->ID) {
            $coupon->update_usage_count();
        }
    }
}
