<?php

/**
 * CancellationFlow REST Controller
 *
 * REST API endpoints for cancellation reasons and retention offers.
 *
 * @package ArraySubs\Features\CancellationFlow\REST
 * @since 1.0.0
 */

namespace ArraySubs\Features\CancellationFlow\REST;

defined('ABSPATH') || exit;

use ArraySubs\Supports\BaseRestController;
use ArraySubs\Features\CancellationFlow\Services\RetentionOfferProcessor;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

/**
 * CancellationFlow REST controller
 */
class CancellationController extends BaseRestController
{
    /**
     * Enable auto-loading
     *
     * @var bool
     */
    public static $loadable = true;

    /**
     * Constructor
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Register REST routes
     *
     * @return void
     */
    public function registerRoutes(): void
    {
        // Get cancellation reasons list
        register_rest_route(
            $this->namespace,
            '/cancellation/reasons',
            [
                'methods' => 'GET',
                'callback' => [$this, 'getCancellationReasons'],
                'permission_callback' => '__return_true', // Public for customer portal
            ]
        );

        // Get cancellation form configuration
        register_rest_route(
            $this->namespace,
            '/cancellation/form-config',
            [
                'methods' => 'GET',
                'callback' => [$this, 'getFormConfig'],
                'permission_callback' => [$this, 'checkAdminPermission'],
            ]
        );

        // Save cancellation form configuration (admin only)
        register_rest_route(
            $this->namespace,
            '/cancellation/form-config',
            [
                'methods' => 'POST',
                'callback' => [$this, 'saveFormConfig'],
                'permission_callback' => [$this, 'checkAdminPermission'],
            ]
        );

        // Get retention offers for a subscription
        register_rest_route(
            $this->namespace,
            '/subscriptions/(?P<id>\d+)/retention-offers',
            [
                'methods' => 'POST',
                'callback' => [$this, 'getRetentionOffers'],
                'permission_callback' => [$this, 'checkSubscriptionAccess'],
                'args' => [
                    'id' => [
                        'required' => true,
                        'type' => 'integer',
                        'sanitize_callback' => 'absint',
                    ],
                    'reason' => [
                        'required' => false,
                        'type' => 'string',
                        'sanitize_callback' => 'sanitize_key',
                        'description' => __('Selected cancellation reason to filter offers', 'arraysubs'),
                    ],
                ],
            ]
        );

        // Accept a retention offer
        register_rest_route(
            $this->namespace,
            '/subscriptions/(?P<id>\d+)/retention-offers/accept',
            [
                'methods' => 'POST',
                'callback' => [$this, 'acceptRetentionOffer'],
                'permission_callback' => [$this, 'checkSubscriptionAccess'],
                'args' => [
                    'id' => [
                        'required' => true,
                        'type' => 'integer',
                        'sanitize_callback' => 'absint',
                    ],
                    'offer_type' => [
                        'required' => true,
                        'type' => 'string',
                        'sanitize_callback' => 'sanitize_text_field',
                    ],
                    'offer_id' => [
                        'required' => false,
                        'type' => 'string',
                        'sanitize_callback' => 'sanitize_key',
                    ],
                ],
            ]
        );

        // Submit cancellation with reason
        register_rest_route(
            $this->namespace,
            '/subscriptions/(?P<id>\d+)/cancel-with-reason',
            [
                'methods' => 'POST',
                'callback' => [$this, 'cancelWithReason'],
                'permission_callback' => [$this, 'checkSubscriptionAccess'],
                'args' => [
                    'id' => [
                        'required' => true,
                        'type' => 'integer',
                        'sanitize_callback' => 'absint',
                    ],
                    'reason' => [
                        'required' => false,
                        'type' => 'string',
                        'sanitize_callback' => 'sanitize_text_field',
                    ],
                    'reason_details' => [
                        'required' => false,
                        'type' => 'string',
                        'sanitize_callback' => 'sanitize_textarea_field',
                    ],
                ],
            ]
        );

        // Get cancellation analytics (admin only)
        register_rest_route(
            $this->namespace,
            '/cancellation/analytics',
            [
                'methods' => 'GET',
                'callback' => [$this, 'getCancellationAnalytics'],
                'permission_callback' => [$this, 'checkAdminPermission'],
                'args' => [
                    'start_date' => [
                        'required' => false,
                        'type' => 'string',
                        'sanitize_callback' => 'sanitize_text_field',
                    ],
                    'end_date' => [
                        'required' => false,
                        'type' => 'string',
                        'sanitize_callback' => 'sanitize_text_field',
                    ],
                ],
            ]
        );
    }

    /**
     * Check admin permission for cancellation management endpoints.
     *
     * @return bool
     */
    public function checkAdminPermission(): bool
    {
        if (!\is_user_logged_in()) {
            return false;
        }

        return \current_user_can('manage_woocommerce') || \current_user_can('manage_options');
    }

    /**
     * Check if user has access to a subscription
     *
     * @param WP_REST_Request $request Request object
     * @return bool
     */
    public function checkSubscriptionAccess(WP_REST_Request $request): bool
    {
        // Must be authenticated for subscription-specific endpoints.
        if (!\is_user_logged_in()) {
            return false;
        }

        // Admins always have access
        if (\current_user_can('manage_woocommerce') || \current_user_can('manage_options')) {
            return true;
        }

        // Check if user owns the subscription
        $subscription_id = \absint($request->get_param('id'));
        $subscription = arraysubs_get_subscription($subscription_id);

        if (!$subscription instanceof \WP_Post) {
            return false;
        }

        $customer_id = \absint(\get_post_meta($subscription_id, '_customer_id', true));
        $current_user_id = \get_current_user_id();

        // Never allow access when ownership meta is missing/invalid.
        if ($customer_id <= 0 || $current_user_id <= 0) {
            return false;
        }

        return $customer_id === $current_user_id;
    }

    /**
     * Get cancellation reasons
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function getCancellationReasons(WP_REST_Request $request): WP_REST_Response
    {
        // Get reasons via the single source-of-truth helper (settings → defaults → filter).
        $reasons = arraysubs_get_cancellation_reasons();

        // Format for frontend (id / label / allow_custom_text)
        $formatted = [];
        foreach ($reasons as $key => $label) {
            $formatted[] = [
                'id'               => $key,
                'label'            => $label,
                'allow_custom_text' => ('other' === $key),
            ];
        }

        // Allow filtering for backwards compatibility
        $formatted = apply_filters('arraysubs_cancellation_reasons', $formatted);

        return arraysubs_rest_response($formatted, 200);
    }

    /**
     * Get cancellation form configuration
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function getFormConfig(WP_REST_Request $request): WP_REST_Response
    {
        $config = arraysubs_get_setting('cancellation.form_config', []);

        // Default form config
        if (empty($config)) {
            $config = [
                'layout' => 'single', // Layout type: single or multi-step
                'require_reason' => arraysubs_require_cancel_reason(),
                'show_retention_offers' => true,
                'fields' => [
                    [
                        'id' => 'reason',
                        'type' => 'radio',
                        'label' => __('Why are you cancelling?', 'arraysubs'),
                        'required' => arraysubs_require_cancel_reason(),
                        'options' => arraysubs_get_cancellation_reasons(),
                    ],
                    [
                        'id' => 'reason_details',
                        'type' => 'textarea',
                        'label' => __('Please tell us more (optional)', 'arraysubs'),
                        'required' => false,
                        'show_when' => ['field' => 'reason', 'value' => 'other'],
                    ],
                ],
            ];
        }

        return arraysubs_rest_response($config, 200);
    }

    /**
     * Save cancellation form configuration
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function saveFormConfig(WP_REST_Request $request): WP_REST_Response
    {
        $config = $request->get_json_params();

        // Validate config structure
        if (!is_array($config)) {
            return new WP_Error(
                'invalid_config',
                __('Invalid form configuration', 'arraysubs'),
                ['status' => 400]
            );
        }

        // Sanitize config
        $sanitized = [
            'layout' => sanitize_text_field($config['layout'] ?? 'single'),
            'require_reason' => (bool) ($config['require_reason'] ?? false),
            'show_retention_offers' => (bool) ($config['show_retention_offers'] ?? true),
            'fields' => [],
        ];

        // Sanitize fields
        if (!empty($config['fields']) && is_array($config['fields'])) {
            foreach ($config['fields'] as $field) {
                $sanitized['fields'][] = [
                    'id' => sanitize_key($field['id'] ?? ''),
                    'type' => sanitize_text_field($field['type'] ?? 'text'),
                    'label' => sanitize_text_field($field['label'] ?? ''),
                    'required' => (bool) ($field['required'] ?? false),
                    'options' => isset($field['options']) ? array_map('sanitize_text_field', (array) $field['options']) : [],
                    'show_when' => isset($field['show_when']) ? [
                        'field' => sanitize_key($field['show_when']['field'] ?? ''),
                        'value' => sanitize_text_field($field['show_when']['value'] ?? ''),
                    ] : null,
                ];
            }
        }

        // Save to settings
        $settings = arraysubs_get_settings();
        $settings['cancellation']['form_config'] = $sanitized;
        arraysubs_save_settings($settings);

        return arraysubs_rest_response(
            $sanitized,
            200,
            __('Form configuration saved', 'arraysubs')
        );
    }

    /**
     * Get retention offers for a subscription
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function getRetentionOffers(WP_REST_Request $request): WP_REST_Response
    {
        $subscription_id = absint($request->get_param('id'));
        $selected_reason = sanitize_key($request->get_param('reason') ?? '');

        // Check if retention offers are enabled
        if (!arraysubs_is_retention_offers_enabled()) {
            return arraysubs_rest_response([], 200);
        }

        // Get offers filtered by reason
        $offers = RetentionOfferProcessor::getAvailableOffers($subscription_id, $selected_reason ?: null);

        // Mark that offers were shown for analytics/history.
        update_post_meta($subscription_id, '_retention_offer_shown', true);
        if (function_exists('arraysubs_track_retention_offers_shown')) {
            arraysubs_track_retention_offers_shown($subscription_id, array_keys($offers));
        } else {
            update_post_meta($subscription_id, '_retention_offers_shown', array_keys($offers));
            update_post_meta($subscription_id, '_retention_offers_shown_date', current_time('mysql', true));
        }

        if ($selected_reason) {
            update_post_meta($subscription_id, '_cancellation_reason_at_offer', $selected_reason);
        }

        /**
         * Fires after retention offers are returned to the customer.
         *
         * @param int   $subscription_id  Subscription ID.
         * @param array $offers           Available offers keyed by offer ID.
         */
        do_action('arraysubs_retention_offer_shown', $subscription_id, $offers);

        // Format for frontend
        $formatted = [];
        foreach ($offers as $offer_id => $offer) {
            $formatted[] = [
                'id' => $offer_id,
                'type' => $offer['type'],
                'title' => $offer['title'],
                'description' => $offer['description'],
                'config' => $offer['config'] ?? [],
            ];
        }

        return arraysubs_rest_response($formatted, 200);
    }

    /**
     * Accept a retention offer
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function acceptRetentionOffer(WP_REST_Request $request): WP_REST_Response
    {
        $subscription_id = absint($request->get_param('id'));
        $offer_type = sanitize_text_field($request->get_param('offer_type'));
        $offer_data = $request->get_json_params() ?? [];
        $offer_id = sanitize_key($request->get_param('offer_id') ?? ($offer_data['offer_id'] ?? ''));

        if (!empty($offer_id)) {
            $available_offers = RetentionOfferProcessor::getAvailableOffers($subscription_id, null);

            if (isset($available_offers[$offer_id]) && is_array($available_offers[$offer_id])) {
                $offer_data = array_merge($available_offers[$offer_id]['config'] ?? [], $offer_data, [
                    'offer_id' => $offer_id,
                    'offer_title' => $available_offers[$offer_id]['title'] ?? '',
                ]);
            } else {
                $offer_data['offer_id'] = $offer_id;
            }
        }

        // Process the offer
        $result = RetentionOfferProcessor::processOffer($subscription_id, $offer_type, $offer_data);

        if (!$result['success']) {
            return new WP_Error(
                'offer_failed',
                $result['message'],
                ['status' => 400]
            );
        }

        return arraysubs_rest_response($result, 200, $result['message']);
    }

    /**
     * Cancel subscription with reason
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function cancelWithReason(WP_REST_Request $request): WP_REST_Response
    {
        $subscription_id = absint($request->get_param('id'));
        $reason = sanitize_text_field($request->get_param('reason') ?? '');
        $reason_details = sanitize_textarea_field($request->get_param('reason_details') ?? '');
        $cancel_type = sanitize_text_field($request->get_param('cancel_type') ?? '');

        $subscription = arraysubs_get_subscription($subscription_id);
        if (!$subscription instanceof \WP_Post) {
            return new WP_Error(
                'not_found',
                __('Subscription not found', 'arraysubs'),
                ['status' => 404]
            );
        }

        if (arraysubs_require_cancel_reason() && empty($reason)) {
            return new WP_Error(
                'reason_required',
                __('Please provide a cancellation reason', 'arraysubs'),
                ['status' => 400]
            );
        }

        // Determine cancellation type - use settings default if not specified
        if (empty($cancel_type)) {
            $cancel_type = arraysubs_cancel_immediately() ? 'immediate' : 'end_of_period';
        }

        // Determine actor — admins get attributed to their user, customers
        // to 'customer'. The canonical helpers store this in _cancelled_by
        // so AutoNotes can build the correct attribution.
        if (current_user_can('manage_woocommerce') || current_user_can('manage_options')) {
            $cancelled_by = 'admin:' . get_current_user_id();
        } else {
            $cancelled_by = 'customer';
        }

        $reason_details_array = [];
        if ($reason !== '') {
            $reason_details_array['reason'] = $reason;
        }
        if ($reason_details !== '') {
            $reason_details_array['details'] = $reason_details;
        }

        if (
            !function_exists('arraysubs_acquire_subscription_mutation_lock')
            || !arraysubs_acquire_subscription_mutation_lock($subscription_id)
        ) {
            return new WP_Error(
                'subscription_conflict',
                __('This subscription is already being updated. Please refresh and try again.', 'arraysubs'),
                ['status' => 409]
            );
        }

        try {
            // Delegate to canonical helpers — same reasoning as the admin
            // SubscriptionController. Crucially, when this endpoint is
            // called on a subscription already in the pending-cancellation
            // state, the immediate path now correctly clears
            // _waiting_cancellation / _cancellation_scheduled_date and marks
            // the override so AutoNotes describes it accurately and the UI
            // does not show conflicting badges.
            if ($cancel_type === 'end_of_period') {
                $ok = arraysubs_schedule_cancellation($subscription_id, $reason, $cancelled_by, $reason_details_array);
            } else {
                $ok = arraysubs_cancel_subscription_immediately($subscription_id, $reason, $cancelled_by, $reason_details_array);
            }

            if (!$ok) {
                return new WP_Error(
                    'cancel_failed',
                    __('Failed to cancel subscription', 'arraysubs'),
                    ['status' => 500]
                );
            }

            $is_scheduled = arraysubs_is_waiting_cancellation($subscription_id);
            $new_status = get_post_status($subscription_id) ?: $subscription->post_status;

            return arraysubs_rest_response(
                [
                    'subscription_id' => $subscription_id,
                    'status' => $new_status,
                    'waiting_cancellation' => $is_scheduled,
                    'cancel_type' => $is_scheduled ? 'end_of_period' : 'immediate',
                ],
                200,
                $is_scheduled
                    ? __('Subscription scheduled for cancellation at the end of your current billing period.', 'arraysubs')
                    : __('Subscription cancelled successfully.', 'arraysubs')
            );
        } finally {
            arraysubs_release_subscription_mutation_lock($subscription_id);
        }
    }

    /**
     * Get cancellation analytics
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function getCancellationAnalytics(WP_REST_Request $request): WP_REST_Response
    {
        global $wpdb;

        $start_date = sanitize_text_field($request->get_param('start_date') ?? gmdate('Y-m-d', time() - (30 * DAY_IN_SECONDS)));
        $end_date = sanitize_text_field($request->get_param('end_date') ?? gmdate('Y-m-d'));

        // Get cancelled subscriptions with reasons
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Complex analytics query with joins
        $cancelled_subs = $wpdb->get_results($wpdb->prepare(
            "SELECT p.ID, p.post_date,
                    pm_reason.meta_value as cancellation_reason,
                    pm_type.meta_value as cancellation_type,
                    pm_age.meta_value as subscription_age_at_cancel,
                    pm_payments.meta_value as payments_at_cancel
             FROM {$wpdb->posts} p
             LEFT JOIN {$wpdb->postmeta} pm_reason ON p.ID = pm_reason.post_id AND pm_reason.meta_key = '_cancellation_reason'
             LEFT JOIN {$wpdb->postmeta} pm_type ON p.ID = pm_type.post_id AND pm_type.meta_key = '_cancellation_type'
             LEFT JOIN {$wpdb->postmeta} pm_age ON p.ID = pm_age.post_id AND pm_age.meta_key = '_subscription_age_at_cancel'
             LEFT JOIN {$wpdb->postmeta} pm_payments ON p.ID = pm_payments.post_id AND pm_payments.meta_key = '_payments_at_cancel'
             WHERE p.post_type = 'arraysubs_data'
             AND p.post_status = 'arraysubs-cancelled'
             AND p.post_date BETWEEN %s AND %s
             ORDER BY p.post_date DESC",
            $start_date . ' 00:00:00',
            $end_date . ' 23:59:59'
        ));

        // Count reasons
        $reason_counts = [];
        $total_cancelled = count($cancelled_subs);
        $total_age = 0;
        $total_payments = 0;

        foreach ($cancelled_subs as $sub) {
            $reason = $sub->cancellation_reason ?: 'not_provided';
            $reason_counts[$reason] = ($reason_counts[$reason] ?? 0) + 1;
            $total_age += absint($sub->subscription_age_at_cancel);
            $total_payments += absint($sub->payments_at_cancel);
        }

        // Get retention offer stats
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Complex analytics query with joins
        $offers_shown = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(DISTINCT p.ID)
             FROM {$wpdb->posts} p
             INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_retention_offer_shown'
             WHERE p.post_type = 'arraysubs_data'
             AND pm.meta_value = '1'
             AND p.post_date BETWEEN %s AND %s",
            $start_date . ' 00:00:00',
            $end_date . ' 23:59:59'
        ));

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Complex analytics query with joins
        $offers_accepted = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(DISTINCT p.ID)
             FROM {$wpdb->posts} p
             INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_retention_offer_accepted'
             WHERE p.post_type = 'arraysubs_data'
             AND pm.meta_value = '1'
             AND p.post_date BETWEEN %s AND %s",
            $start_date . ' 00:00:00',
            $end_date . ' 23:59:59'
        ));

        $analytics = [
            'period' => [
                'start' => $start_date,
                'end' => $end_date,
            ],
            'total_cancelled' => $total_cancelled,
            'avg_age_at_cancel_days' => $total_cancelled > 0 ? round($total_age / $total_cancelled) : 0,
            'avg_payments_before_cancel' => $total_cancelled > 0 ? round($total_payments / $total_cancelled, 1) : 0,
            'reasons' => $reason_counts,
            'retention_offers' => [
                'shown' => absint($offers_shown),
                'accepted' => absint($offers_accepted),
                'acceptance_rate' => $offers_shown > 0 ? round(($offers_accepted / $offers_shown) * 100, 1) : 0,
            ],
        ];

        return arraysubs_rest_response($analytics, 200);
    }
}
