<?php

/**
 * Customer Actions REST Controller
 *
 * REST API endpoints for customer subscription actions.
 *
 * @package ArraySubs\Features\CustomerPortal\REST
 * @since 1.0.0
 */

namespace ArraySubs\Features\CustomerPortal\REST;

defined('ABSPATH') || exit;

use ArraySubs\Supports\BaseRestController;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

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

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

    /**
     * Register REST routes
     *
     * @return void
     */
    public function registerRoutes(): void
    {
        // Cancel subscription (customer action)
        register_rest_route(
            $this->namespace,
            '/my-subscriptions/(?P<id>\d+)/cancel',
            [
                'methods' => 'POST',
                'callback' => [$this, 'cancelSubscription'],
                'permission_callback' => [$this, 'checkCustomerPermission'],
                'args' => [
                    'id' => [
                        'required' => true,
                        'type' => 'integer',
                        'sanitize_callback' => 'absint',
                    ],
                    'reason' => [
                        'required' => false,
                        'type' => 'string',
                        'sanitize_callback' => 'sanitize_text_field',
                    ],
                    'other_reason' => [
                        'required' => false,
                        'type' => 'string',
                        'sanitize_callback' => 'sanitize_textarea_field',
                    ],
                ],
            ]
        );

        // Undo scheduled cancellation (customer action)
        register_rest_route(
            $this->namespace,
            '/my-subscriptions/(?P<id>\d+)/undo-cancellation',
            [
                'methods' => 'POST',
                'callback' => [$this, 'undoCancellation'],
                'permission_callback' => [$this, 'checkCustomerPermission'],
                'args' => [
                    'id' => [
                        'required' => true,
                        'type' => 'integer',
                        'sanitize_callback' => 'absint',
                    ],
                ],
            ]
        );

        // Reactivate cancelled subscription (customer action)
        register_rest_route(
            $this->namespace,
            '/my-subscriptions/(?P<id>\d+)/reactivate',
            [
                'methods' => 'POST',
                'callback' => [$this, 'reactivateSubscription'],
                'permission_callback' => [$this, 'checkCustomerPermission'],
                'args' => [
                    'id' => [
                        'required' => true,
                        'type' => 'integer',
                        'sanitize_callback' => 'absint',
                    ],
                ],
            ]
        );

        // Retry failed renewal payment (customer action)
        register_rest_route(
            $this->namespace,
            '/my-subscriptions/(?P<id>\d+)/retry-payment',
            [
                'methods' => 'POST',
                'callback' => [$this, 'retryPayment'],
                'permission_callback' => [$this, 'checkCustomerPermission'],
                'args' => [
                    'id' => [
                        'required' => true,
                        'type' => 'integer',
                        'sanitize_callback' => 'absint',
                    ],
                ],
            ]
        );

        // Get shipping address (customer action)
        register_rest_route(
            $this->namespace,
            '/my-subscriptions/(?P<id>\d+)/shipping-address',
            [
                [
                    'methods' => 'GET',
                    'callback' => [$this, 'getShippingAddress'],
                    'permission_callback' => [$this, 'checkCustomerPermission'],
                    'args' => [
                        'id' => [
                            'required' => true,
                            'type' => 'integer',
                            'sanitize_callback' => 'absint',
                        ],
                    ],
                ],
                [
                    'methods' => 'POST',
                    'callback' => [$this, 'updateShippingAddress'],
                    'permission_callback' => [$this, 'checkCustomerPermission'],
                    'args' => [
                        'id' => [
                            'required' => true,
                            'type' => 'integer',
                            'sanitize_callback' => 'absint',
                        ],
                        'shipping_address' => [
                            'required' => true,
                            'type' => 'object',
                        ],
                    ],
                ],
            ]
        );
    }

    /**
     * Check customer permission
     *
     * @param WP_REST_Request $request Request object
     * @return bool|WP_Error True if has permission
     */
    public function checkCustomerPermission(WP_REST_Request $request)
    {
        if (!is_user_logged_in()) {
            return new WP_Error(
                'not_logged_in',
                __('You must be logged in', 'arraysubs'),
                ['status' => 401]
            );
        }

        $subscription_id = $request->get_param('id');
        $subscription = arraysubs_get_subscription($subscription_id);

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

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

        if ($customer_id <= 0) {
            return new WP_Error(
                'invalid_subscription_owner',
                __('Invalid subscription ownership data', 'arraysubs'),
                ['status' => 403]
            );
        }

        if ($current_user_id !== $customer_id) {
            return new WP_Error(
                'permission_denied',
                __('You do not have permission to manage this subscription', 'arraysubs'),
                ['status' => 403]
            );
        }

        return true;
    }

    /**
     * Cancel subscription (customer action)
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error Response
     */
    public function cancelSubscription(WP_REST_Request $request)
    {
        $subscription_id = (int) $request->get_param('id');
        $reason = (string) $request->get_param('reason');
        $other_reason = (string) $request->get_param('other_reason');

        if ($subscription_id <= 0) {
            return new WP_Error(
                'invalid_subscription',
                __('Invalid subscription ID', 'arraysubs'),
                ['status' => 400]
            );
        }

        if (!function_exists('arraysubs_can_customer_cancel_by_subs_id')
            || !arraysubs_can_customer_cancel_by_subs_id($subscription_id)) {
            return new WP_Error(
                'cancellation_disabled',
                __('Customer cancellation is not available. Please contact support.', 'arraysubs'),
                ['status' => 403]
            );
        }

        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 {
            $status = get_post_status($subscription_id) ?: '';

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

            // Delegate to the canonical helper. It chooses immediate vs. scheduled
            // based on the cancel_immediately setting and fires the canonical
            // arraysubs_data_cancelled / arraysubs_data_waiting_cancellation
            // actions that downstream listeners (notes, emails, retention
            // analytics) rely on.
            $cancelled = arraysubs_cancel_subscription(
                $subscription_id,
                $reason,
                'customer',
                $reason_details
            );

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

            $is_scheduled = arraysubs_is_waiting_cancellation($subscription_id);
            $new_status = get_post_status($subscription_id) ?: $status;

            // Customer-specific extension hooks for third-party integrations that
            // need to distinguish customer-initiated cancellations from admin or
            // gateway-initiated ones.
            if ($is_scheduled) {
                do_action('arraysubs_data_pending_cancel_by_customer', $subscription_id, $reason);
                $response_message = __('Subscription scheduled for cancellation at the end of your current billing period.', 'arraysubs');
            } else {
                do_action('arraysubs_data_cancelled_by_customer', $subscription_id, $reason);
                $response_message = __('Subscription cancelled successfully.', 'arraysubs');
            }

            return arraysubs_rest_response(
                ['subscription_id' => $subscription_id, 'status' => $new_status],
                200,
                $response_message
            );
        } finally {
            arraysubs_release_subscription_mutation_lock($subscription_id);
        }
    }

    /**
     * Undo a scheduled cancellation (customer action)
     *
     * Allows customers to undo a scheduled end-of-period cancellation.
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error Response
     */
    public function undoCancellation(WP_REST_Request $request)
    {
        $subscription_id = (int) $request->get_param('id');
        $user_id = get_current_user_id();

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

        $customer_id = (int) get_post_meta($subscription_id, '_customer_id', true);
        if ($customer_id !== $user_id) {
            return new WP_Error(
                'unauthorized',
                __('You do not have permission to modify this subscription', 'arraysubs'),
                ['status' => 403]
            );
        }

        if (!function_exists('arraysubs_can_undo_cancellation')
            || !arraysubs_can_undo_cancellation($subscription_id)) {
            return new WP_Error(
                'not_scheduled',
                __('This subscription is not scheduled for cancellation', 'arraysubs'),
                ['status' => 400]
            );
        }

        // Delegate to the canonical helper so the queued ActionScheduler
        // cancellation is unscheduled and downstream listeners (notes,
        // emails) reset state via arraysubs_data_cancellation_undone.
        $undone = arraysubs_undo_scheduled_cancellation($subscription_id, 'customer');

        if (!$undone) {
            return new WP_Error(
                'undo_failed',
                __('Failed to remove scheduled cancellation', 'arraysubs'),
                ['status' => 500]
            );
        }

        // Customer-specific extension hook for third-party integrations.
        do_action('arraysubs_data_cancellation_undone_by_customer', $subscription_id);

        return arraysubs_rest_response(
            [
                'subscription_id' => $subscription_id,
                'status' => $subscription->post_status,
            ],
            200,
            __('Scheduled cancellation removed successfully. Your subscription will continue renewing normally.', 'arraysubs')
        );
    }

    /**
     * Reactivate a cancelled or expired subscription (customer action)
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error Response
     */
    public function reactivateSubscription(WP_REST_Request $request)
    {
        $subscription_id = $request->get_param('id');

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

        $current_status = $subscription->post_status;

        // Only cancelled or expired subscriptions can be reactivated by customers
        $reactivatable_statuses = ['arraysubs-cancelled', 'arraysubs-expired'];
        if (!in_array($current_status, $reactivatable_statuses, true)) {
            return new WP_Error(
                'cannot_reactivate',
                __('This subscription cannot be reactivated from its current status.', 'arraysubs'),
                ['status' => 400]
            );
        }

        if (!function_exists('arraysubs_can_customer_reactivate_by_subs_id')
            || !arraysubs_can_customer_reactivate_by_subs_id($subscription_id)) {
            return new WP_Error(
                'reactivation_disabled',
                __('Customer reactivation is not available. Please contact support.', 'arraysubs'),
                ['status' => 403]
            );
        }

        $result = wp_update_post([
            'ID' => $subscription_id,
            'post_status' => 'arraysubs-active',
        ], true);

        if (is_wp_error($result)) {
            return $result;
        }

        if ($result === 0) {
            return new WP_Error(
                'update_failed',
                __('Failed to reactivate subscription', 'arraysubs'),
                ['status' => 500]
            );
        }

        // Clear lifecycle meta that no longer applies after reactivation
        delete_post_meta($subscription_id, '_waiting_cancellation');
        delete_post_meta($subscription_id, '_cancellation_scheduled_date');
        delete_post_meta($subscription_id, '_cancellation_reason');
        delete_post_meta($subscription_id, '_cancellation_reason_details');
        delete_post_meta($subscription_id, '_cancellation_type');
        delete_post_meta($subscription_id, '_cancellation_override_scheduled');
        delete_post_meta($subscription_id, '_cancelled_by');
        delete_post_meta($subscription_id, '_end_date');

        if (function_exists('arraysubs_add_subscription_note')) {
            arraysubs_add_subscription_note(
                $subscription_id,
                __('Subscription reactivated by customer from the customer portal.', 'arraysubs'),
                'customer',
                get_current_user_id(),
                [],
                'subscription'
            );
        }

        // Fire reactivation hooks for downstream integrations (notes, emails,
        // gateway validation). The status change to 'arraysubs-active' will
        // also trigger arraysubs_data_reactivated via OrderIntegration.
        do_action('arraysubs_subscription_reactivated', $subscription_id);
        do_action('arraysubs_data_reactivated_by_customer', $subscription_id, $current_status);

        return arraysubs_rest_response(
            [
                'subscription_id' => $subscription_id,
                'status' => 'arraysubs-active',
                'old_status' => $current_status,
            ],
            200,
            __('Subscription reactivated successfully', 'arraysubs')
        );
    }

    /**
     * Retry a failed renewal payment (customer action).
     *
     * Wraps {@see RenewalProcessor::manualRetry()}. The processor first
     * verifies with the gateway whether the customer was already charged via a
     * missed webhook (so we never double-charge); if not, it attempts the
     * payment again. Card-decline reasons are recorded as subscription notes.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response|WP_Error Response.
     */
    public function retryPayment(WP_REST_Request $request)
    {
        $subscription_id = (int) $request->get_param('id');
        $subscription    = arraysubs_get_subscription($subscription_id);

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

        $processor = new \ArraySubs\Features\RecurringBilling\Services\RenewalProcessor();
        $result    = $processor->manualRetry($subscription_id, 'customer');

        if (! empty($result['success'])) {
            return arraysubs_rest_response(
                [
                    'subscription_id' => $subscription_id,
                    'status'          => $result['status'],
                    'order_id'        => $result['order_id'] ?? 0,
                ],
                200,
                $result['message']
            );
        }

        return new WP_Error(
            'retry_failed',
            $result['message'] ?? __('Renewal payment retry failed.', 'arraysubs'),
            [
                'status'   => 'no_failed_renewal' === ($result['status'] ?? '') ? 409 : 402,
                'order_id' => $result['order_id'] ?? 0,
            ]
        );
    }

    /**
     * Get shipping address for subscription
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error Response
     */
    public function getShippingAddress(WP_REST_Request $request)
    {
        $subscription_id = $request->get_param('id');
        $subscription = arraysubs_get_subscription($subscription_id);

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

        // Check if subscription needs shipping
        $needs_shipping = get_post_meta($subscription_id, '_arraysubs_needs_shipping', true);
        if ($needs_shipping !== 'yes') {
            return new WP_Error(
                'no_shipping',
                __('This subscription does not require shipping', 'arraysubs'),
                ['status' => 400]
            );
        }

        // Get shipping address
        $shipping_address_json = get_post_meta($subscription_id, '_shipping_address', true);
        $shipping_address = !empty($shipping_address_json) ? json_decode($shipping_address_json, true) : [];

        // Get next payment date for cutoff check
        $next_payment_date = get_post_meta($subscription_id, '_next_payment_date', true);
        $can_update = true;
        $cutoff_message = '';

        if (!empty($next_payment_date)) {
            $cutoff_days = apply_filters('arraysubs_shipping_address_cutoff_days', 3);
            $cutoff_time = strtotime($next_payment_date) - ($cutoff_days * DAY_IN_SECONDS);

            if (time() >= $cutoff_time) {
                $can_update = false;
                $cutoff_message = sprintf(
                    /* translators: %d: number of days */
                    __('Shipping address cannot be changed within %d days of the next payment.', 'arraysubs'),
                    $cutoff_days
                );
            }
        }

        return arraysubs_rest_response(
            [
                'shipping_address' => $shipping_address,
                'needs_shipping' => true,
                'shipping_type' => get_post_meta($subscription_id, '_arraysubs_shipping_type', true) ?: 'recurring',
                'can_update' => $can_update,
                'cutoff_message' => $cutoff_message,
                'next_payment_date' => $next_payment_date,
            ],
            200,
            __('Shipping address retrieved', 'arraysubs')
        );
    }

    /**
     * Update shipping address for subscription (customer action)
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error Response
     */
    public function updateShippingAddress(WP_REST_Request $request)
    {
        $subscription_id = $request->get_param('id');
        $subscription = arraysubs_get_subscription($subscription_id);

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

        // Check if subscription needs shipping
        $needs_shipping = get_post_meta($subscription_id, '_arraysubs_needs_shipping', true);
        if ($needs_shipping !== 'yes') {
            return new WP_Error(
                'no_shipping',
                __('This subscription does not require shipping', 'arraysubs'),
                ['status' => 400]
            );
        }

        // Check cutoff period
        $next_payment_date = get_post_meta($subscription_id, '_next_payment_date', true);
        if (!empty($next_payment_date)) {
            $cutoff_days = apply_filters('arraysubs_shipping_address_cutoff_days', 3);
            $cutoff_time = strtotime($next_payment_date) - ($cutoff_days * DAY_IN_SECONDS);

            if (time() >= $cutoff_time) {
                return new WP_Error(
                    'cutoff_reached',
                    sprintf(
                        /* translators: %d: number of days */
                        __('Shipping address cannot be changed within %d days of the next payment.', 'arraysubs'),
                        $cutoff_days
                    ),
                    ['status' => 400]
                );
            }
        }

        // Get and validate shipping address
        $shipping_address = $request->get_param('shipping_address');
        if (empty($shipping_address) || !is_array($shipping_address)) {
            return new WP_Error(
                'invalid_address',
                __('Invalid shipping address', 'arraysubs'),
                ['status' => 400]
            );
        }

        // Validate required fields
        $required_fields = ['first_name', 'last_name', 'address_1', 'city', 'postcode', 'country'];
        foreach ($required_fields as $field) {
            if (empty($shipping_address[$field])) {
                return new WP_Error(
                    'missing_field',
                    sprintf(
                        /* translators: %s: field name */
                        __('Missing required field: %s', 'arraysubs'),
                        $field
                    ),
                    ['status' => 400]
                );
            }
        }

        // Sanitize address fields
        $allowed_fields = [
            'first_name',
            'last_name',
            'company',
            'address_1',
            'address_2',
            'city',
            'state',
            'postcode',
            'country',
            'phone'
        ];

        $sanitized_address = [];
        foreach ($allowed_fields as $field) {
            if (isset($shipping_address[$field])) {
                $sanitized_address[$field] = sanitize_text_field($shipping_address[$field]);
            }
        }

        // Get existing address for comparison
        $existing_address_json = get_post_meta($subscription_id, '_shipping_address', true);
        $existing_address = !empty($existing_address_json) ? json_decode($existing_address_json, true) : [];

        // Merge with existing address
        $merged_address = array_merge(
            is_array($existing_address) ? $existing_address : [],
            $sanitized_address
        );

        // Save the address
        update_post_meta($subscription_id, '_shipping_address', wp_json_encode($merged_address));

        // Add subscription note
        if (function_exists('arraysubs_add_subscription_note')) {
            $address_labels = [
                'first_name' => __('Shipping first name', 'arraysubs'),
                'last_name' => __('Shipping last name', 'arraysubs'),
                'company' => __('Shipping company', 'arraysubs'),
                'address_1' => __('Shipping address line 1', 'arraysubs'),
                'address_2' => __('Shipping address line 2', 'arraysubs'),
                'city' => __('Shipping city', 'arraysubs'),
                'state' => __('Shipping state', 'arraysubs'),
                'postcode' => __('Shipping postcode', 'arraysubs'),
                'country' => __('Shipping country', 'arraysubs'),
                'phone' => __('Shipping phone', 'arraysubs'),
            ];
            $address_changes = [];
            $audit_changes = [];

            foreach ($address_labels as $field => $label) {
                $old_value = arraysubs_format_audit_value($existing_address[$field] ?? '');
                $new_value = arraysubs_format_audit_value($merged_address[$field] ?? '');

                if ($old_value === $new_value) {
                    continue;
                }

                $address_changes[] = arraysubs_format_audit_change_sentence($label, $old_value, $new_value);
                $audit_changes[] = arraysubs_create_audit_change_entry($label, $old_value, $new_value);
            }

            $note = __('Shipping address updated by customer.', 'arraysubs');

            if (!empty($address_changes)) {
                $note .= '<br>' . implode(
                    '<br>',
                    array_map(
                        static fn(string $change): string => '• ' . esc_html($change),
                        $address_changes
                    )
                );
            }

            arraysubs_add_subscription_note(
                $subscription_id,
                $note,
                'customer',
                get_current_user_id(),
                $audit_changes,
                'subscription'
            );
        }

        // Trigger action for integrations
        do_action('arraysubs_shipping_address_updated_by_customer', $subscription_id, $merged_address);

        return arraysubs_rest_response(
            [
                'shipping_address' => $merged_address,
            ],
            200,
            __('Shipping address updated successfully', 'arraysubs')
        );
    }
}
