<?php
/**
 * The subscription module.
 *
 * @package WooCommerce\PayPalCommerce\WcSubscriptions
 */

declare(strict_types=1);

namespace WooCommerce\PayPalCommerce\PayPalSubscriptions;

use Psr\Log\LoggerInterface;
use WC_Product;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingPlans;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\CatalogProducts;
use WooCommerce\PayPalCommerce\ApiClient\Entity\BillingCycle;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\BillingCycleFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentPreferencesFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ProductFactory;
use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter;
use WooCommerce\PayPalCommerce\ApiClient\Helper\ItemTrait;

/**
 * Class SubscriptionsApiHandler
 */
class SubscriptionsApiHandler {

	use ItemTrait;

	/**
	 * Catalog products.
	 *
	 * @var CatalogProducts
	 */
	private $products_endpoint;

	/**
	 * Product factory.
	 *
	 * @var ProductFactory
	 */
	private $product_factory;

	/**
	 * Billing plans.
	 *
	 * @var BillingPlans
	 */
	private $billing_plans_endpoint;

	/**
	 * Billing cycle factory.
	 *
	 * @var BillingCycleFactory
	 */
	private $billing_cycle_factory;

	/**
	 * Payment preferences factory.
	 *
	 * @var PaymentPreferencesFactory
	 */
	private $payment_preferences_factory;

	/**
	 * The currency.
	 *
	 * @var CurrencyGetter
	 */
	private CurrencyGetter $currency;

	/**
	 * The logger.
	 *
	 * @var LoggerInterface
	 */
	private $logger;

	/**
	 * SubscriptionsApiHandler constructor.
	 *
	 * @param CatalogProducts           $products_endpoint Products endpoint.
	 * @param ProductFactory            $product_factory Product factory.
	 * @param BillingPlans              $billing_plans_endpoint Billing plans endpoint.
	 * @param BillingCycleFactory       $billing_cycle_factory Billing cycle factory.
	 * @param PaymentPreferencesFactory $payment_preferences_factory Payment preferences factory.
	 * @param CurrencyGetter            $currency The currency.
	 * @param LoggerInterface           $logger The logger.
	 */
	public function __construct(
		CatalogProducts $products_endpoint,
		ProductFactory $product_factory,
		BillingPlans $billing_plans_endpoint,
		BillingCycleFactory $billing_cycle_factory,
		PaymentPreferencesFactory $payment_preferences_factory,
		CurrencyGetter $currency,
		LoggerInterface $logger
	) {
		$this->products_endpoint           = $products_endpoint;
		$this->product_factory             = $product_factory;
		$this->billing_plans_endpoint      = $billing_plans_endpoint;
		$this->billing_cycle_factory       = $billing_cycle_factory;
		$this->payment_preferences_factory = $payment_preferences_factory;
		$this->currency                    = $currency;
		$this->logger                      = $logger;
	}

	/**
	 * Creates a Catalog Product and adds it as WC product meta.
	 *
	 * @param WC_Product $product The WC product.
	 * @return void
	 */
	public function create_product( WC_Product $product ) {
		try {
			$subscription_product = $this->products_endpoint->create( $this->prepare_item_string( $product->get_title() ), $this->prepare_item_string( $product->get_description() ) );
			$product->update_meta_data( 'ppcp_subscription_product', $subscription_product->to_array() );
			$product->save();
		} catch ( RuntimeException $exception ) {
			$error = $exception->getMessage();
			if ( is_a( $exception, PayPalApiException::class ) ) {
				$error = $exception->get_details( $error );
			}

			$this->logger->error( 'Could not create catalog product on PayPal. ' . $error );
		}
	}

	/**
	 * Creates a subscription plan.
	 *
	 * @param string     $plan_name The plan name.
	 * @param WC_Product $product The WC product.
	 * @return void
	 */
	public function create_plan( string $plan_name, WC_Product $product ): void {
		try {
			$subscription_plan = $this->billing_plans_endpoint->create(
				$plan_name ?: $product->get_title(),
				$product->get_meta( 'ppcp_subscription_product' )['id'] ?? '',
				$this->billing_cycles( $product ),
				$this->payment_preferences_factory->from_wc_product( $product )->to_array()
			);

			$product->update_meta_data( 'ppcp_subscription_plan', $subscription_plan->to_array() );
			$product->save();
		} catch ( RuntimeException $exception ) {
			$error = $exception->getMessage();
			if ( is_a( $exception, PayPalApiException::class ) ) {
				$error = $exception->get_details( $error );
			}

			$this->logger->error( 'Could not create subscription plan on PayPal. ' . $error );
		}
	}

	/**
	 * Updates a product.
	 *
	 * @param WC_Product $product The WC product.
	 * @return void
	 */
	public function update_product( WC_Product $product ): void {
		try {
			$catalog_product_id = $product->get_meta( 'ppcp_subscription_product' )['id'] ?? '';
			if ( $catalog_product_id ) {
				$catalog_product             = $this->products_endpoint->product( $catalog_product_id );
				$catalog_product_name        = $catalog_product->name() ?: '';
				$catalog_product_description = $catalog_product->description() ?: '';

				$wc_product_description = $this->prepare_item_string( $product->get_description() ) ?: $this->prepare_item_string( $product->get_title() );

				if ( $catalog_product_name !== $product->get_title() || $catalog_product_description !== $wc_product_description ) {
					$data = array();
					if ( $catalog_product_name !== $product->get_title() ) {
						$data[] = (object) array(
							'op'    => 'replace',
							'path'  => '/name',
							'value' => $product->get_title(),
						);
					}
					if ( $catalog_product_description !== $wc_product_description ) {
						$data[] = (object) array(
							'op'    => 'replace',
							'path'  => '/description',
							'value' => $wc_product_description,
						);
					}

					$this->products_endpoint->update( $catalog_product_id, $data );
				}
			}
		} catch ( RuntimeException $exception ) {
			$error = $exception->getMessage();
			if ( is_a( $exception, PayPalApiException::class ) ) {
				$error = $exception->get_details( $error );
			}

			$this->logger->error( 'Could not update catalog product on PayPal. ' . $error );
		}
	}

	/**
	 * Updates a plan.
	 *
	 * @param WC_Product $product The WC product.
	 * @return void
	 */
	public function update_plan( WC_Product $product ): void {
		try {
			$subscription_plan_id = $product->get_meta( 'ppcp_subscription_plan' )['id'] ?? '';
			if ( $subscription_plan_id ) {
				$subscription_plan = $this->billing_plans_endpoint->plan( $subscription_plan_id );

				$price = $subscription_plan->billing_cycles()[0]->pricing_scheme()['fixed_price']['value'] ?? '';
				if ( $price && round( (float) $price, 2 ) !== round( (float) $product->get_price(), 2 ) ) {
					$this->billing_plans_endpoint->update_pricing(
						$subscription_plan_id,
						$this->billing_cycle_factory->from_wc_product( $product )
					);
				}
			}
		} catch ( RuntimeException $exception ) {
			$error = $exception->getMessage();
			if ( is_a( $exception, PayPalApiException::class ) ) {
				$error = $exception->get_details( $error );
			}

			$this->logger->error( 'Could not update subscription plan on PayPal. ' . $error );
		}
	}

	/**
	 * Returns billing cycles based on WC Subscription product.
	 *
	 * @param WC_Product $product The WC Subscription product.
	 * @return array
	 */
	private function billing_cycles( WC_Product $product ): array {
		$billing_cycles = array();
		$sequence       = 1;

		$trial_length = $product->get_meta( '_subscription_trial_length' ) ?? '';
		if ( $trial_length ) {
			$billing_cycles[] = ( new BillingCycle(
				array(
					'interval_unit'  => $product->get_meta( '_subscription_trial_period' ),
					'interval_count' => $product->get_meta( '_subscription_trial_length' ),
				),
				$sequence,
				'TRIAL',
				array(
					'fixed_price' => array(
						'value'         => '0',
						'currency_code' => $this->currency->get(),
					),
				),
				1
			) )->to_array();

			$sequence++;
		}

		$billing_cycles[] = ( new BillingCycle(
			array(
				'interval_unit'  => $product->get_meta( '_subscription_period' ),
				'interval_count' => $product->get_meta( '_subscription_period_interval' ),
			),
			$sequence,
			'REGULAR',
			array(
				'fixed_price' => array(
					'value'         => $product->get_meta( '_subscription_price' ) ?: $product->get_price(),
					'currency_code' => $this->currency->get(),
				),
			),
			(int) $product->get_meta( '_subscription_length' )
		) )->to_array();

		return $billing_cycles;
	}
}
