<?php

namespace App\Services;

use App\Exceptions\ApiHttpException;
use App\Models\SubPlan;
use App\Models\Subscription;
use App\Models\SubscriptionPlan;
use App\Models\SubscriptionPlanOption;
use App\Models\User;
use BcMath\Number;
use Illuminate\Support\Facades\DB;
use Throwable;

class SubscriptionsService
{
    /**
     * Creates a Subscription for a given account based on the passed Subscription Plan Option
     *
     * @param User $user
     * @param integer $subscriptionOptionId
     * @param integer|null $subscriptionRewardId
     * @return Subscription
     */
    public function create(
        User $user,
        int $subscriptionOptionId = null,
        int $subscriptionRewardId = null
    ): Subscription
    {
        $account = $user->ownAccount;

        if (!$account) {
            throw new ApiHttpException(403, trans('frontend.exception_user_without_account'));
        }

        if (empty($subscriptionOptionId)) {
            if (empty($user->registration_plan_option_id)) {
                throw new ApiHttpException(400, trans('frontend.exception_not_valid_plan_option'));
            } else {
                $subscriptionOptionId = $user->registration_plan_option_id;
            }
        }
        $planOption = SubscriptionPlanOption::find($subscriptionOptionId);
        if (!$planOption) {
            throw new ApiHttpException(400, trans('frontend.exception_not_valid_plan_option'));
        }
        $plan = $planOption->subscriptionPlan;

        $subscriptionReward = null;
        if (!empty($subscriptionRewardId)) {
            $subPlan = SubPlan::query()
                ->where('subscription_plan_id', $plan->id)
                ->first();

            if (empty($subPlan)) {
                throw new ApiHttpException(400, trans('frontend.exception_not_valid_subscription_reward'));
            }

            $subscriptionReward = $subPlan->subscriptionRewards()
                ->where('subscription_rewards.id', $subscriptionRewardId)
                ->first();

            if (empty($subscriptionReward)) {
                throw new ApiHttpException(400, trans('frontend.exception_not_valid_subscription_reward'));
            }
        }

        if ($account->activeSubscriptions->first()) {
            throw new ApiHttpException(400, trans('frontend.exception_exists_active_subscription'));
        }

        DB::beginTransaction();
        try {
            $subscription = new Subscription();
            $subscription->start_date = now();
            $subscription->trial_end_date = now()->addDays(30);
            $subscription->end_date = now()->addYear()->addDays(30);
            $subscription->status = Subscription::STATUS_ACTIVE;
            $subscription->price = $planOption->price;
            $subscription->periodicity = $planOption->periodicity;
            $subscription->is_trial = true;
            $subscription->seats = $planOption->seats;
            $subscription->subscriptionPlan()->associate($plan);
            $subscription->account()->associate($account);

            if (!empty($subscriptionReward)) {
                $subscription->subscriptionReward()->associate($subscriptionReward);
            }

            $user->registration_step = User::REGISTRATION_STEP_COMPLETED;
            $user->save();

            $subscription->save();

            DB::commit();
            return $subscription;
        } catch (Throwable $e) {
            DB::rollback();
            throw $e;
        }
    }

    /**
     * Retrieves a subscription plan by its ID
     *
     * @param Number $planId
     * @return SubscriptionPlanOption|null
     */
    public function getSubscriptionPlanOption($planId)
    {
        return SubscriptionPlanOption::find($planId);
    }

    /**
     * Retrieves a paginated list of subscription plans for the given data
     *
     * @param Array $data
     * @return void
     */
    public function getSubscriptionPlans(Array $data)
    {
        $query = SubscriptionPlan::query();

        if (!empty($data['search'])) {
            $query->where('name', 'like', '%' . $data['search'] . '%');
        }

        if (!empty($data['sort'])) {
            $query->orderBy($data['sort']['key'], $data['sort']['order']);
        }

        return $query->paginate($data['itemsPerPage']);
    }

    public function saveSubscriptionPlan($data, $subscriptionPlan = null)
    {
        DB::beginTransaction();
        try {
            $existingOptionsIds = [];
            $currentOptionsIds = [];
            if (empty($subscriptionPlan)) {
                $subscriptionPlan = new SubscriptionPlan();
            } else {
                $existingOptionsIds = $subscriptionPlan->options->pluck('id')->toArray();
            }

            $subscriptionPlan->name = $data['name'];
            $subscriptionPlan->save();

            $subscriptionPlan->features()->sync($data['features']);

            foreach ($data['options'] as $option) {
                if (!empty($option['id'])) {
                    $planOption = SubscriptionPlanOption::find($option['id']);
                } else {
                    $planOption = new SubscriptionPlanOption();
                }

                $planOption->name = $option['name'];
                $planOption->price = $option['price'];
                $planOption->seats = $option['seats'];
                $planOption->periodicity = $option['periodicity'];
                $planOption->subscriptionPlan()->associate($subscriptionPlan);
                $planOption->save();
                $currentOptionsIds[] = $planOption->id;
            }

            SubscriptionPlanOption::whereIn('id', array_diff($existingOptionsIds, $currentOptionsIds))->delete();
            $subscriptionPlan->refresh();
            DB::commit();
            return $subscriptionPlan;
        } catch (Throwable $e) {
            DB::rollback();
            throw $e;
        }
    }

    public function processProgrammedCancellations()
    {
        $subscriptions = Subscription::where('status', Subscription::STATUS_ACTIVE)
            ->whereNotNull('cancel_on')
            ->where('cancel_on', '<=', now())
            ->get();

        foreach ($subscriptions as $subscription) {
            $subscription->status = Subscription::STATUS_INACTIVE;
            $subscription->save();
        }
    }
}
