<?php
/**
 * BookiFlex Payment Extension
 *
 * @package    BookiFlex
 * @subpackage Payment
 * @author     BookiFlex
 * @copyright  Copyright (c) 2025 BookiFlex
 * @license    GPL-2.0-or-later
 *
 * @link       https://bookiflex.com
 */

declare(strict_types=1);

 namespace BookiFlex\Extensions\Payment\Api\Controller;

use BookiFlex\Core\Api\Controller\AbstractController;
use BookiFlex\Core\Contract\RoleCapabilitiesInterface;
use BookiFlex\Core\Exception\Exception as CoreException;
use BookiFlex\Core\Exception\NotFoundException;
use BookiFlex\Extensions\Payment\Api\Request\CaptureAuthorizedRequest;
use BookiFlex\Extensions\Payment\Api\Request\GetPaymentRequest;
use BookiFlex\Extensions\Payment\Api\Request\GetPrivateTokenRequest;
use BookiFlex\Extensions\Payment\Api\Request\GetPublicTokenRequest;
use BookiFlex\Extensions\Payment\Api\Request\MarkAsPaidRequest;
use BookiFlex\Extensions\Payment\Api\Request\SyncPaymentRequest;
use BookiFlex\Extensions\Payment\Api\Transformer\PaymentTransformer;
use BookiFlex\Extensions\Payment\Provider\PaymentProvider;
use BookiFlex\Extensions\Payment\Service\PaymentManager;
use BookiFlex\Extensions\Payment\Service\PaymentInitiator;
use Throwable;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;

defined( 'ABSPATH' ) || exit;

class PaymentController extends AbstractController
{
    public const API_ENDPOINT_NAME = 'payment';

    public function __construct(
        private readonly PaymentProvider $findPayment,
        private readonly PaymentManager $paymentManager,
        private readonly PaymentInitiator $paymentInitiator,
        string                          $namespace = 'bflex/v1'
    ) {
        parent::__construct($namespace);
    }

    /**
     * Register payment REST API routes
     *
     * Note: Gateway callback routes (capture, authorize, done, cancel, notify)
     * are registered in PaymentCallbackController
     */
    public function registerRoutes(): void
    {
        register_rest_route($this->namespace, '/' . self::API_ENDPOINT_NAME . '/item/(?P<id>\d+)', [
            [
                'methods'             => 'GET',
                'callback'            => [$this, 'getPayment'],
                'permission_callback' => [$this, 'hasAccess'],
                'args'                => GetPaymentRequest::getValidationRules(),
            ],
        ]);

        register_rest_route($this->namespace, '/' . self::API_ENDPOINT_NAME . '/token/capture', [
            [
                'methods'             => 'POST',
                'callback'            => [$this, 'createPrivateCaptureToken'],
                'permission_callback' => [$this, 'hasAccess'],
                'args'                => GetPrivateTokenRequest::getValidationRules(),
            ],
        ]);

        register_rest_route($this->namespace, '/' . self::API_ENDPOINT_NAME . '/token/cancel', [
            [
                'methods'             => 'POST',
                'callback'            => [$this, 'createPrivateCancelToken'],
                'permission_callback' => [$this, 'hasAccess'],
                'args'                => GetPrivateTokenRequest::getValidationRules(),
            ],
        ]);

        register_rest_route($this->namespace, '/' . self::API_ENDPOINT_NAME . '/sync', [
            [
                'methods'             => 'POST',
                'callback'            => [$this, 'syncPayment'],
                'permission_callback' => [$this, 'hasAccess'],
                'args'                => SyncPaymentRequest::getValidationRules(),
            ],
        ]);

        register_rest_route($this->namespace, '/' . self::API_ENDPOINT_NAME . '/markAsPaid', [
            [
                'methods'             => 'POST',
                'callback'            => [$this, 'markAsPaid'],
                'permission_callback' => [$this, 'hasAccess'],
                'args'                => MarkAsPaidRequest::getValidationRules(),
            ],
        ]);

        register_rest_route($this->namespace, '/' . self::API_ENDPOINT_NAME . '/captureAuthorized', [
            [
                'methods'             => 'POST',
                'callback'            => [$this, 'captureAuthorizedPayment'],
                'permission_callback' => [$this, 'hasAccess'],
                'args'                => CaptureAuthorizedRequest::getValidationRules(),
            ],
        ]);

        // Public endpoint: allows guests to continue payment by unique reservation SID (UUID string) (no authentication required)
        register_rest_route($this->namespace, '/' . self::API_ENDPOINT_NAME . '/token/public/(?P<reservationSid>[a-zA-Z0-9\-]+)/continue', [
            [
                'methods'             => 'GET',
                'callback'            => [$this, 'createPublicCaptureToken'],
                'permission_callback' => '__return_true',
                'args'                => GetPublicTokenRequest::getValidationRules(),
            ],
        ]);
    }

    /**
     * GET /payment/item/{id} - Get payment information
     */
    public function getPayment(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        $requestData = GetPaymentRequest::fromWpRestRequest($request);

        try {
            $payment = $this->findPayment->find($requestData->getId());
            if ($payment === null) {
                throw new NotFoundException('Payment not found.', 404);
            }

            return $this->success([
                'status' => 'success',
                'result' => [
                    'payment' => PaymentTransformer::transform($payment)
                ]
            ]);
        } catch (CoreException $e) {
            $context = $e->getContext();
            $errorCode = $context['errorCode'] ?? 'unknown_error';

            return $this->error(
                'validation_failed',
                $e->getMessage(),
                $e->getCode() ?: 400,
                [
                    'params' => [
                        $errorCode => $e->getMessage()
                    ]
                ]
            );
        } catch (Throwable $e) {
            return $this->handleError(
                $e,
                'payment_load_failed',
                __('Failed to load a payment information.', 'bookiflex'),
            );
        }
    }

    /**
     * POST /payment/token/capture - Create private capture token for manager
     */
    public function createPrivateCaptureToken(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        $requestData = GetPrivateTokenRequest::fromWpRestRequest($request);

        try {
            $url = $this->paymentInitiator->createPrivateCaptureUrl(
                $requestData->getReservationSid(),
                $requestData->getPaymentId()
            );
            return $this->success([
                'status' => 'success',
                'result' => [
                    'token' => $url,
                ]
            ]);
        } catch (CoreException $e) {
            $context = $e->getContext();
            $errorCode = $context['errorCode'] ?? 'unknown_error';

            return $this->error(
                'validation_failed',
                $e->getMessage(),
                $e->getCode() ?: 400,
                [
                    'params' => [
                        $errorCode => $e->getMessage()
                    ]
                ]
            );
        } catch (Throwable $e) {
            return $this->handleError(
                $e,
                'payment_generate_token_failed',
                __('Failed to generate token for payment.', 'bookiflex'),
            );
        }
    }

    /**
     * POST /payment/token/cancel - Create private cancel token for manager
     */
    public function createPrivateCancelToken(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        $requestData = GetPrivateTokenRequest::fromWpRestRequest($request);

        try {
            $url = $this->paymentInitiator->createPrivateCancelUrl(
                $requestData->getReservationSid(),
                $requestData->getPaymentId()
            );
            return $this->success([
                'status' => 'success',
                'result' => [
                    'token' => $url,
                ]
            ]);
        } catch (CoreException $e) {
            $context = $e->getContext();
            $errorCode = $context['errorCode'] ?? 'unknown_error';

            return $this->error(
                'validation_failed',
                $e->getMessage(),
                $e->getCode() ?: 400,
                [
                    'params' => [
                        $errorCode => $e->getMessage()
                    ]
                ]
            );
        } catch (Throwable $e) {
            return $this->handleError(
                $e,
                'payment_generate_token_failed',
                __('Failed to generate token for payment.', 'bookiflex'),
            );
        }
    }

    /**
     * GET /payment/token/public/{reservationSid}/continue - Create public capture token for customer
     */
    public function createPublicCaptureToken(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        $requestData = GetPublicTokenRequest::fromWpRestRequest($request);

        try {
            $url = $this->paymentInitiator->createPublicCaptureUrl($requestData->getReservationSid());
            if ($requestData->isRedirect()) {
                return new WP_REST_Response(null, 302, [
                    'Location' => $url
                ]);
            }

            return $this->success([
                'status' => 'success',
                'result' => [
                    'token' => $url,
                ]
            ]);
        } catch (CoreException $e) {
            $context = $e->getContext();
            $errorCode = $context['errorCode'] ?? 'unknown_error';

            return $this->error(
                'validation_failed',
                $e->getMessage(),
                $e->getCode() ?: 400,
                [
                    'params' => [
                        $errorCode => $e->getMessage()
                    ]
                ]
            );
        } catch (Throwable $e) {
            return $this->handleError(
                $e,
                'payment_generate_token_failed',
                __('Failed to generate token for payment.', 'bookiflex'),
            );
        }
    }

    /**
     * POST /payment/sync - Re-sync payment status with the gateway (for stuck WAITING payments)
     */
    public function syncPayment(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        $requestData = SyncPaymentRequest::fromWpRestRequest($request);

        try {
            $this->paymentManager->syncPayment($requestData->getPaymentId());
            return $this->success([
                'status' => 'success',
            ]);
        } catch (CoreException $e) {
            $context = $e->getContext();
            $errorCode = $context['errorCode'] ?? 'unknown_error';

            return $this->error(
                'validation_failed',
                $e->getMessage(),
                $e->getCode() ?: 400,
                [
                    'params' => [
                        $errorCode => $e->getMessage()
                    ]
                ]
            );
        } catch (Throwable $e) {
            return $this->handleError(
                $e,
                'payment_sync_failed',
                __('Failed to sync payment status.', 'bookiflex'),
            );
        }
    }

    /**
     * POST /payment/markAsPaid - Manually mark a payment as paid (bypasses gateway)
     */
    public function markAsPaid(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        $requestData = MarkAsPaidRequest::fromWpRestRequest($request);

        try {
            $this->paymentManager->markAsPaid($requestData->getPaymentId());
            return $this->success([
                'status' => 'success',
            ]);
        } catch (CoreException $e) {
            $context = $e->getContext();
            $errorCode = $context['errorCode'] ?? 'unknown_error';

            return $this->error(
                'validation_failed',
                $e->getMessage(),
                $e->getCode() ?: 400,
                [
                    'params' => [
                        $errorCode => $e->getMessage()
                    ]
                ]
            );
        } catch (Throwable $e) {
            return $this->handleError(
                $e,
                'payment_mark_as_paid_failed',
                __('Failed to manually confirm payment.', 'bookiflex'),
            );
        }
    }

    /**
     * POST /payment/captureAuthorized - Capture an authorized payment
     * @deprecated (instead we use: create token + capture)
     */
    public function captureAuthorizedPayment(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        $requestData = CaptureAuthorizedRequest::fromWpRestRequest($request);

        try {
            $this->paymentManager->captureAuthorizedPayment($requestData->getPaymentId());
            return $this->success([
                'status' => 'success',
            ]);
        } catch (CoreException $e) {
            $context = $e->getContext();
            $errorCode = $context['errorCode'] ?? 'unknown_error';

            return $this->error(
                'validation_failed',
                $e->getMessage(),
                $e->getCode() ?: 400,
                [
                    'params' => [
                        $errorCode => $e->getMessage()
                    ]
                ]
            );
        } catch (Throwable $e) {
            return $this->handleError(
                $e,
                'payment_capture_failed',
                __('Failed to capture an authorized payment.', 'bookiflex'),
            );
        }
    }

    /**
     * Check if current user has access to payment management
     */
    public function hasAccess(): bool|WP_Error
    {
        if (!current_user_can(RoleCapabilitiesInterface::MANAGE_PAYMENTS)) {
            return $this->error(
                'bookiflex_forbidden',
                __('You are not allowed to manage payments.', 'bookiflex'),
                401
            );
        }

        return true;
    }
}
