<?php

namespace apexl\Io\modules\subscription\services;

use apexl\Config\Singleton;
use apexl\Io\includes\Hook;
use apexl\Io\includes\Routes;
use apexl\Io\includes\System;
use apexl\Io\modules\company\entities\companyEntity;
use apexl\Io\modules\display\components\SimpleButton;
use apexl\Io\modules\invoice\entities\invoiceEntity;
use apexl\Io\modules\payment\entities\paymentEntity;
use apexl\Io\modules\payment\entities\userCreditEntity;
use apexl\Io\modules\payment\providers\stripeProvider;
use apexl\Io\modules\payment\services\cardValidationService;
use apexl\Io\modules\payment\services\paymentService;
use apexl\Io\modules\product\entities\productEntity;
use apexl\Io\modules\subscription\entities\subscriptionChangeEntity;
use apexl\Io\modules\subscription\entities\subscriptionCreditEntity;
use apexl\Io\modules\subscription\entities\subscriptionEntity;
use apexl\Io\modules\user\entities\userEntity;
use apexl\Io\modules\user\services\currentUser;
use apexl\Io\services\Database;
use apexl\Io\services\Logger;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

class subscriptionService {

    protected $config;

    public function __construct()
    {
        $this->config = Singleton::getInstance();
    }

    public function isSubscriptionUser($userId=null)
    {
// I'm afraid we need to do it this way to be sure of not introducing errors by adding new role for subscription customers only
        if (!$userId) $userId = currentUser::getPreAuthedUserId();

        if (!$userId) return false;

        $subscriptionEntity = new subscriptionEntity();
        $subscriptions = $subscriptionEntity->getByUser($userId);

        foreach ($subscriptions as $subscription) {
            if ($subscription->enabled == 1) return true;
        }

        return false;

    }

    public function rollForwardSubscriptions($subscriptions, $setLastBillDate=true)
    {
        foreach ($subscriptions as $subscription) {
            $this->rollSubscriptionForward($subscription, $setLastBillDate);
        }
    }

    public function rollSubscriptionForward($subscription, $setLastBillDate=true)
    {
        if (!$subscription instanceof subscriptionEntity) {
            $id = $subscription->id;
            $subscription = new subscriptionEntity();
            $subscription->load($id);
        }

        $date = new \DateTime('now'); // Set last and next billing dates for subscription
        if ($setLastBillDate) $subscription->last_billing_date = $date->format('Y-m-d');
        if (isset($subscription->next_billing_date) && $subscription->next_billing_date) {
            $date = \DateTime::createFromFormat('Y-m-d', $subscription->next_billing_date);
        }

        if (isset($this->config->app->payments->subscriptions->billingInterval) && $this->config->app->payments->subscriptions->billingInterval) {
            $date->add(new \DateInterval($this->config->app->payments->subscriptions->billingInterval));
        } elseif (isset($this->config->app->payments->subscriptions->specificBillingDate) && $this->config->app->payments->subscriptions->specificBillingDate) {
            $date->modify($this->config->app->payments->subscriptions->specificBillingDate);
        } else {
            $date->add(new \DateInterval('P1M'));
        }
        $subscription->next_billing_date = $date->format('Y-m-d');

        $subscription->store();
    }

    public function createSubscription($companyId, $userId, $productId, $productQuantity, $startDate=null, $gracePeriodEnds=null, $nextBillingDate=null, $temporaryPriceOverride=null)
    {
        $subscription = new subscriptionEntity();

        // Look for existing subscriptions for this user, if exists, update to trigger hooks, and use that
        if (isset($this->config->app->payments->subscriptions->reuseExistingSubscriptions) && $this->config->app->payments->subscriptions->reuseExistingSubscriptions) {
            $sub = $subscription->getByUserCompanyProductAndDate($userId, $companyId, $productId, date('Y-m-d'));
        }
        if (isset($sub) && isset($sub['id']) && $sub['id']) {
            $subscription->load($sub['id']);
            $subscription->product_quantity = $subscription->product_quantity + $productQuantity;
        } else {
            $subscription->company_id = $companyId;
            $subscription->user_id = $userId;
            if ($startDate instanceof \DateTime) {
                $subscription->date_starts = $startDate->format('Y-m-d');
            } else {
                $subscription->date_starts = !is_null($startDate) ? $startDate : date('Y-m-d');
            }

            if (isset($this->config->app->payments->subscriptions->disableOnCreation) && $this->config->app->payments->subscriptions->disableOnCreation) {
                $subscription->enabled = 0;
            } else {
            $subscription->enabled = 1;
            }
            $subscription->created = date('Y-m-d H:i:s');
            $subscription->product_id = $productId;
            $subscription->product_quantity = $productQuantity;
            $subscription->code = bin2hex(random_bytes(32)); // unique code to use in email links

            if ($nextBillingDate) {
                if ($nextBillingDate instanceof \DateTime) {
                    $date = $nextBillingDate;
                } else {
                $date = \DateTime::createFromFormat('Y-m-d', $nextBillingDate);
                }
            }  else {
                $date = new \DateTime('now');
            }
            $subscription->next_billing_date = $date->format('Y-m-d');

            if ($gracePeriodEnds) {
                if ($gracePeriodEnds instanceof \DateTime) {
                    $subscription->grace_period_ends = $gracePeriodEnds->format('Y-m-d');
                } else {
                    $subscription->grace_period_ends = $gracePeriodEnds;
                }
            }

            if (isset($temporaryPriceOverride) && $temporaryPriceOverride->price > 0) {
                $subscription->product_price_override = $temporaryPriceOverride->price;
                $subscription->product_price_override_ends = $temporaryPriceOverride->ends;
            }

            if (isset($this->config->app->payments->subscriptions->defaultPaymentProvider) && $this->config->app->payments->subscriptions->defaultPaymentProvider != '') {
                $subscription->payment_provider = $this->config->app->payments->subscriptions->defaultPaymentProvider;
            }
        }

        $subscription->store(); // Always store (even for existing subs), in order to trigger hooks

        return $subscription;
    }

    public function createSubscriptionPaymentLinks($payment)
    {

        $upserts = [];
        foreach ($this->subscriptions as $subscription) {
            $upserts[] = [
                'subscription_id' => $subscription->id,
                'payment_id' => $payment->id,
            ];
        }
        if (count($upserts) > 0) {
            Database::persistRawBatch($upserts, 'subscription_payment');
        }
    }

    public function reenableUserSubscriptions($user, $reason="Successful payment")
    {
        $subscriptionEntity = new subscriptionEntity();
        $subscriptions = $subscriptionEntity->getByUser($user->id);
        $now = date('Y-m-d');
        foreach ($subscriptions as $subscription) {
            $subscriptionEntity->load($subscription->id);
            if ($subscriptionEntity->enabled == '0' && $subscriptionEntity->suspended == '1') {
                if (!$subscriptionEntity->date_expires || $subscriptionEntity->date_expires > $now) {
                    $subscriptionEntity->enabled = 1;
                    $subscriptionEntity->cancellation_date = null;
                    $subscriptionEntity->store();
                    Hook::processHook('subscription_enabled', $subscriptionEntity, $reason);
                }
            }
        }
    }

    public function createSubscriptionsWithToken($user, $paymentToken, $startDate=null, $gracePeriodEnds=null, $nextBillingDate=null)
    {
        $subscriptions = [];

        $companyId = null;
        if (class_exists('apexl\Io\modules\company\entities\companyEntity')) {
            $company = new companyEntity();
            $company->loadByUser($user->id);
            $companyId = $company ? $company->id : null;
        }
        $data = json_decode($paymentToken->data);
        if (isset($data->products) && is_array($data->products)) { //i.e. if this is a woocommerce order
            $productEntity = new productEntity();
            foreach ($data->products as $product) {
                if (isset($product->product_id)) {
                    $productEntity->loadByFrontendId($product->product_id);
                    $productId = $productEntity->id;
                } elseif (isset($product->product_name)) { // If no ID provided, load by name
                    $productEntity->loadByName($product->product_name);
                    if (!isset($productEntity->id) || !$productEntity) { // Automatically create new product if it doesn't exist
                        $productEntity = new productEntity();
                        $productEntity->type = $product->type ?? "";
                        $productEntity->product_name = $product->product_name;
                        $productEntity->price = $product->price ?? 0;
                        $productEntity->code = $product->code ?? "";
                        $productEntity->created = time();
                        $productEntity->store();
                    }
                    $productId = $productEntity->id;
                } else if (isset($product->dashboardProductId)) {
                    $productId = $product->dashboardProductId;
                }
                if ((int)$productId > 0 && $productEntity->has_subscription > 0) {
                    $subscriptions[] = $this->createSubscription($companyId, $user->id, $productId, $product->quantity, $startDate, $gracePeriodEnds, $nextBillingDate, $data->productPriceOverride ?? NULL);
                }

            }
        } else { // None-woocommerce order - may be extinct now? Not sure...
            $subscriptions[] = $this->createSubscription($companyId, $user->id, $this->getProductIdFromToken($paymentToken), 1, $startDate, $gracePeriodEnds, $nextBillingDate, $data->productPriceOverride ?? NULL);
        }

        return $subscriptions;
    }

    public function getBestsellerInfo(\DateTime $start, \DateTime $end)
    {
        $bestseller = (object) [
            'name' => '',
            'value' => 0,
            'valueRaw' => 0,
        ];

        $productEntity = new productEntity();
        $productNamesById = $productEntity->getProductNameCache('id');
        $productNamesByCode = $productEntity->getProductNameCache('code');

        $invoiceEntity = new invoiceEntity();
        $invoices = $invoiceEntity->getSubscriptionInvoices($start, $end);
        $productValues = [];
        foreach ($invoices as $invoice) {
            $lineItems = json_decode($invoice->line_items);
            foreach ($lineItems as $item) {
                if (isset($item->productId)) {
                    $productName = null;
                    if (isset($productNamesById[$item->productId])) $productName = $productNamesById[$item->productId];
                    elseif (isset($productNamesByCode[$item->productId])) $productName = $productNamesByCode[$item->productId];
                    if ($productName && $this->isLineItemScheduledPayment($item, $invoice)) {
                        if (!isset($productValues[$productName])) $productValues[$productName] = 0;
                        $productValues[$productName] += round((float)$item->amount,2);
                    }
                }
            }
        }

        arsort($productValues);

        foreach ($productValues as $name => $value) {
            $bestseller->name = $name . ' Subscription';
            $bestseller->value = number_format($value, 2);
            $bestseller->valueRaw = $value;
            break;
        }

        return $bestseller;
    }

    public function isLineItemScheduledPayment($item, $invoice=null)
    {
        $result = false;

        if ($invoice && $invoice->remote_order_id > 0) $result = false;

        $desc = $item->description ?? ($item->Description ?? ($item->name ?? 'N/A'));
        $refString = strtolower(str_replace(" ", "", $desc));
        $subscriptionChecks = [
            "monthlyfee",
            "subscriptionfee",
            "scheduledbillingdate",
            "overage",
            "issuedate",
        ];
        foreach ($subscriptionChecks as $check) {
            if (strpos($refString, $check) !== false) $result = true;
        }

        if ($invoice && $invoice->remote_order_id == 0) $result = true;

        return $result;
    }

    public function suspendedStatus($subscriptionEntity)
    {

        $statusObject = (object)['canSuspend' => true, 'label' => 'Suspend can be suspended', 'bsStyle' => 'danger'];

        if (!$subscriptionEntity->enabled) {
            return (object)['canSuspend' => false, 'label' => '', 'bsStyle' => 'warning'];
        }
        if ($subscriptionEntity->cancellation_date > '0000-00-01 00:00:01') {
            $cancellationDate = \DateTime::createFromFormat('Y-m-d', $subscriptionEntity->cancellation_date);
            $statusObject = (object)['canSuspend' => false, 'label' => 'Subscription scheduled for cancellation '.$cancellationDate->format('jS M Y'), 'bsStyle' => 'warning'];
        }

        $statusObject = Hook::processHook('modify_subscription_suspend_status_object', $subscriptionEntity, $statusObject);

        return $statusObject;
    }

    public function suspendAllUserSubscriptions($userEntity)
    {
        $subscriptionEntity = new subscriptionEntity();
        $subscriptions = $subscriptionEntity->getByUser($userEntity->id);

        foreach ($subscriptions as $subscription) {
            $subscriptionEntity->load($subscription->id);
            $this->suspendSubscription($subscriptionEntity, 'full_user_suspension');
        }
    }

    public function suspendSubscription(subscriptionEntity $subscriptionEntity, $logName='payment_failure_suspension', $cancellationDate=null, $cancellationReason="")
    {
        $subscriptionEntity->suspended = 1;
        $subscriptionEntity->suspension_date = date('Y-m-d H:i:s');
        if ($cancellationDate) { // Y-m-d
            $subscriptionEntity->cancellation_date = $cancellationDate;
            $subscriptionEntity->cancellation_reason = $cancellationReason;
        } else {
            $subscriptionEntity->enabled = 0;
            $subscriptionEntity->cancellation_date = date('Y-m-d');
        }
        $subscriptionEntity->store();

        $logData = (object)($subscriptionEntity->getData());
        $logData->currentUri = filter_var($_SERVER['REQUEST_URI'], FILTER_SANITIZE_STRING);
        Logger::log($logName, $logData);

        Hook::processHook('subscription_disabled', $subscriptionEntity, $cancellationDate);

        return $subscriptionEntity;
    }

    public function changeSubscriptionProductNow(subscriptionEntity $subscriptionEntity, $newProductId, \DateTimeInterface $lastBillingDate=null, \DateTimeInterface $nextBillingDate=null)
    {
        $oldProductId = $subscriptionEntity->product_id;
        $subscriptionEntity->product_id = $newProductId;
        if ($lastBillingDate) $subscriptionEntity->last_billing_date = $lastBillingDate->format('Y-m-d');
        if ($nextBillingDate) $subscriptionEntity->next_billing_date = $nextBillingDate->format('Y-m-d');
        $subscriptionEntity->store();
        Logger::log('subscription_change_product_changed', [
            'subscription' => $subscriptionEntity->getData(),
            'newProductId' => $newProductId,
            'lastBillingDate' => $lastBillingDate ? $lastBillingDate->format('Y-m-d') : null,
            'nextBillingDate' => $nextBillingDate ? $nextBillingDate->format('Y-m-d') : null,
        ]);

        $subscriptionChangeEntity = new subscriptionChangeEntity();
        $subscriptionChangeEntity->timestamp = date('Y-m-d H:i:s');
        $subscriptionChangeEntity->subscription_id = $subscriptionEntity->id;
        $subscriptionChangeEntity->previous_product_id = $oldProductId;
        $subscriptionChangeEntity->new_product_id = $newProductId;
        $subscriptionChangeEntity->store();

        Hook::processHook('subscription_post_change_product', $subscriptionEntity, $subscriptionChangeEntity);
    }

    public function restartSubscriptionProductNow(subscriptionEntity $subscriptionEntity, $newProductId, \DateTimeInterface $lastBillingDate=null, \DateTimeInterface $nextBillingDate=null)
    {
        $oldProductId = $subscriptionEntity->product_id;
        $subscriptionEntity->product_id = $newProductId;
        if ($lastBillingDate) $subscriptionEntity->last_billing_date = $lastBillingDate->format('Y-m-d');
        if ($nextBillingDate) $subscriptionEntity->next_billing_date = $nextBillingDate->format('Y-m-d');
        $subscriptionEntity->store();
        Logger::log('subscription_change_product_changed', [
            'subscription' => $subscriptionEntity->getData(),
            'newProductId' => $newProductId,
            'lastBillingDate' => $lastBillingDate ? $lastBillingDate->format('Y-m-d') : null,
            'nextBillingDate' => $nextBillingDate ? $nextBillingDate->format('Y-m-d') : null,
        ]);

        $subscriptionChangeEntity = new subscriptionChangeEntity();
        $subscriptionChangeEntity->timestamp = date('Y-m-d H:i:s');
        $subscriptionChangeEntity->subscription_id = $subscriptionEntity->id;
        $subscriptionChangeEntity->previous_product_id = $oldProductId;
        $subscriptionChangeEntity->new_product_id = $newProductId;
        $subscriptionChangeEntity->store();

        Hook::processHook('subscription_post_change_product', $subscriptionEntity, $subscriptionChangeEntity);
    }

}