<?php
/**
 * Copyright (C) Baluart.COM - All Rights Reserved
 *
 * @since 1.0
 * @author Baluart E.I.R.L.
 * @copyright Copyright (c) 2015 - 2021 Baluart E.I.R.L.
 * @license http://codecanyon.net/licenses/faq Envato marketplace licenses
 * @link https://easyforms.dev/ Easy Forms
 */

namespace app\modules\subscription;

use app\components\behaviors\UserPreferences;
use app\controllers\AjaxController;
use app\controllers\AppController;
use app\controllers\FormController;
use app\controllers\user\RegistrationController;
use app\controllers\user\SecurityController;
use app\helpers\ArrayHelper;
use app\helpers\Html;
use app\helpers\TimeHelper;
use app\models\Form;
use app\models\forms\RegistrationForm;
use app\models\FormSubmission;
use app\models\User;
use app\modules\subscription\models\Subscription;
use app\modules\update\helpers\Setup;
use Da\User\Event\FormEvent;
use Da\User\Event\UserEvent;
use Da\User\Form\LoginForm;
use kartik\sidenav\SideNav;
use Yii;
use yii\base\ActionEvent;
use yii\base\BootstrapInterface;
use yii\base\Event;
use yii\filters\AccessControl;
use yii\helpers\Json;
use yii\helpers\Url;
use yii\web\Response;
use yii\web\View;

/**
 * Subscriptions module definition class
 *
 * Class Module
 * @package app\modules\subscription
 */
class Module extends \yii\base\Module implements BootstrapInterface
{

    const MIGRATION_PATH = '@app/modules/subscription/migrations';

    public $defaultRoute = 'admin/index';
    public $controllerLayout = '@app/views/layouts/main';
    public $params = [];

    /**
     * @inheritdoc
     */
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::class,
                'rules' => [
                    [
                        'allow' => true,
                        'matchCallback' => function () {
                            return true;
                        }
                    ],
                ],
            ],
        ];
    }

    /**
     * @inheritdoc
     */
    public function init()
    {
        parent::init();

        $this->params = [
            // Pricing table
            'pricing' => [
                'switcher' => true, // Show pricing switcher: true or false
                'intervals' => [
                    'month' => Yii::t('app', 'Monthly'),
                    'year' => Yii::t('app', 'Yearly'),
                ],
            ]
        ];

        $this->setAliases([
            '@subscription-assets' => __DIR__ . '/assets'
        ]);
    }

    /**
     * Bootstrap method to be called during application bootstrap stage.
     *
     * @param \yii\base\Application $app the application currently running
     */
    public function bootstrap($app)
    {
        // Enable migration process
        Event::on(Setup::class, Setup::EVENT_BEFORE_MIGRATION , [Module::class, 'onSetupHelperBeforeMigration']);

        // If table exists
        $tableName = $app->db->tablePrefix . 'subscription';
        if (in_array($tableName, $app->db->schema->getTableNames())) {
            $this->attachEvents($app);
            $this->addUrlRules($app);
        }
    }

    /**
     * Add friendly urls
     *
     * @param $app
     */
    public function addUrlRules($app)
    {
        // Update default application route
        if (isset(Yii::$app->user->isGuest) && Yii::$app->user->isGuest) {
            $app->defaultRoute = 'subscription/public/index';
        } else {
            $app->defaultRoute = 'dashboard/index';
        }

        // Adds new routes to the application
        $app->getUrlManager()->addRules([
            'user/settings/subscription' => 'subscription/user/index',
            'user/settings/subscription/billing-info' => 'subscription/user/billing-info',
            'user/settings/subscription/change' => 'subscription/user/change',
            'user/settings/subscription/confirm' => 'subscription/user/confirm',
            'user/settings/subscription/receipts' => 'subscription/user/receipts',
            'user/settings/subscription/receipt' => 'subscription/user/receipt',
            'user/settings/subscription/create' => 'subscription/user/create',
            'user/settings/subscription/cancel' => 'subscription/user/cancel',
            'user/settings/subscription/cancel-now' => 'subscription/user/cancel-now',
            'user/settings/subscription/change-paypal-plan' => 'subscription/user/change-paypal-plan',
            'user/settings/subscription/change-stripe-plan' => 'subscription/user/change-stripe-plan',
        ], false);
    }

    /**
     * @inheritdoc
     */
    public function attachEvents($app)
    {
        // Attach class events
        Event::on(SideNav::class, SideNav::EVENT_BEFORE_RUN , [Module::class, 'onSideNavBeforeRun']);
        Event::on(View::class, View::EVENT_AFTER_RENDER , [Module::class, 'onViewAfterRender']);
        Event::on(FormController::class, FormController::EVENT_BEFORE_ACTION , [Module::class, 'onFormControllerBeforeAction']);
        Event::on(AjaxController::class, AjaxController::EVENT_BEFORE_ACTION , [Module::class, 'onAjaxControllerBeforeAction']);
        Event::on(AppController::class, AppController::EVENT_BEFORE_ACTION , [Module::class, 'onAppControllerBeforeAction']);
        Event::on(RegistrationController::class, FormEvent::EVENT_AFTER_REGISTER, [Module::class, 'onRegistrationControllerAfterRegister']);
        Event::on(RegistrationController::class, UserEvent::EVENT_AFTER_CONFIRMATION, [Module::class, 'onRegistrationControllerAfterConfirmation']);
        Event::on(SecurityController::class, FormEvent::EVENT_AFTER_LOGIN , [Module::class, 'onSecurityControllerAfterLogin']);
    }

    /**
     * On Setup Helper Before Migration
     *
     * @param $event
     */
    public static function onSetupHelperBeforeMigration($event)
    {
        $sender = $event->sender;
        $migrationPath = Yii::getAlias(self::MIGRATION_PATH);

        if (!in_array($migrationPath, $sender->migrationPath)) {
            $sender->migrationPath[] = $migrationPath;
        }

        $event->sender = $sender;
    }

    /**
     * Event Handler
     * After a view is rendered
     *
     * @param $event
     */
    public static function onViewAfterRender($event)
    {
        if (isset($event->sender, $event->sender->context, $event->sender->context->module, $event->sender->context->module->requestedRoute)
            && in_array($event->sender->context->module->requestedRoute, ['form/share', 'form/popup-code', 'form/popup-preview', 'app/form', 'app/forms'])
        ) {
            $content = $event->output;
            $event->output = str_replace("EasyForms", "FormWidget", $content);
        }
    }

    /**
     * On App Controller Before Action
     *
     * @param ActionEvent $event
     */
    public static function onAppControllerBeforeAction($event)
    {

        if (isset($event->action->id) && $event->action->id === 'f') {

            /** @var AppController $appController */
            $appController = $event->action->controller;

            if ($appController->request->isPost) {

                $form = $appController->getFormModel();

                if ($form !== null) {

                    $auth = Yii::$app->authManager;
                    $settings = Yii::$app->settings;

                    /**
                     * Check Trial Period
                     */
                    $defaultRole = $settings->get('app.defaultUserRole');
                    $days = $settings->get('subscription.trialDays');
                    if (!empty($defaultRole) && !empty($days) && is_numeric($days)) {
                        $time = strtotime('-' . $days . ' day', time());
                        $user = $form->author;

                        if ($user->created_at < $time) {
                            $roles = $auth->getRolesByUser($user->id);
                            $roles = ArrayHelper::getColumn($roles, 'name');
                            if (in_array($defaultRole, $roles)) {
                                // Remove default user role
                                $authRole = $auth->getRole($defaultRole);
                                $auth->revoke($authRole, $user->id);
                                // Disable forms
                                Form::updateAll(['status' => Form::OFF], ['created_by' => $user->id]);
                            } elseif (empty($roles)) { // If user doesn't have any role
                                // Disable forms
                                Form::updateAll(['status' => Form::OFF], ['created_by' => $user->id]);
                            }
                        }
                    }

                    /**
                     * Check Submission Limit
                     */
                    $startTime = TimeHelper::startTime('m');
                    $submissions = FormSubmission::find()
                        ->select('id')
                        ->where(['form_id' => $form->id])
                        ->andWhere(['between','created_at', $startTime, time()])
                        ->count();

                    // Check by default user role
                    $submissionLimit = $settings->get('subscription.submissionLimit');
                    if (!empty($defaultRole)) {
                        $roles = $auth->getRolesByUser($form->created_by);
                        $roles = ArrayHelper::getColumn($roles, 'name');
                        if (count($roles) === 1 && in_array($defaultRole, $roles)) {
                            // Check limit
                            if ($submissionLimit <= $submissions) {
                                // Send response
                                self::submissionLimitHasBeenBeached($event);
                            }
                        }
                    }

                    // Check by subscription
                    /** @var Subscription $subscription */
                    $subscription = Subscription::find()
                        ->where(['user_id' => $form->created_by])
                        ->andWhere(['or', ['ends_at' => null], ['>', 'ends_at', time()]])
                        ->one();
                    if ($subscription
                        && !empty($subscription->submission_limit)
                        && $subscription->submission_limit <= $submissions) {
                        // Send response
                        self::submissionLimitHasBeenBeached($event);
                    }
                }
            }
        }
    }

    /**
     * On Form Controller Before Action
     *
     * @param ActionEvent $event
     */
    public static function onFormControllerBeforeAction($event)
    {
        /**
         * Check Form Limit
         */
        if (isset($event->action->id) && $event->action->id === 'create') {

            $userID = Yii::$app->user->id;
            $forms = Form::find()->where(['created_by' => $userID])->count();

            // Check by default user role
            $defaultRole = Yii::$app->settings->get('app.defaultUserRole');
            $formLimit = Yii::$app->settings->get('subscription.formLimit');
            if (!empty($defaultRole)) {
                $roles = Yii::$app->authManager->getRolesByUser($userID);
                $roles = ArrayHelper::getColumn($roles, 'name');
                if (count($roles) === 1 && in_array($defaultRole, $roles)) {
                    // Check limit
                    if ($formLimit <= $forms) {
                        // Send response
                        self::formLimitHasBeenReached($event);
                    }
                }
            }

            // Check by subscription
            /** @var Subscription $subscription */
            $subscription = Subscription::find()
                ->where(['user_id' => $userID])
                ->andWhere(['or', ['ends_at' => null], ['>', 'ends_at', time()]])
                ->one();

            if ($subscription
                && !empty($subscription->form_limit)
                && $subscription->form_limit <= $forms) {
                // Send response
                self::formLimitHasBeenReached($event);
            }
        }
    }

    /**
     * On Ajax Controller Before Action
     *
     * @param ActionEvent $event
     */
    public static function onAjaxControllerBeforeAction($event)
    {
        /**
         * Check Form Limit
         */
        if (isset($event->action->id) && $event->action->id === 'create-form') {

            $userID = Yii::$app->user->id;
            $forms = Form::find()->where(['created_by' => $userID])->count();

            // Check by default user role
            $defaultRole = Yii::$app->settings->get('app.defaultUserRole');
            $formLimit = Yii::$app->settings->get('subscription.formLimit');
            if (!empty($defaultRole)) {
                $roles = Yii::$app->authManager->getRolesByUser($userID);
                $roles = ArrayHelper::getColumn($roles, 'name');
                if (count($roles) === 1 && in_array($defaultRole, $roles)) {
                    // Check limit
                    if ($formLimit <= $forms) {
                        // Send response
                        self::formLimitHasBeenReached($event, true);
                    }
                }
            }

            // Check by subscription
            /** @var Subscription $subscription */
            $subscription = Subscription::find()
                ->where(['user_id' => $userID])
                ->andWhere(['or', ['ends_at' => null], ['>', 'ends_at', time()]])
                ->one();

            // Check limit
            if ($subscription
                && !empty($subscription->form_limit)
                && $subscription->form_limit <= $forms) {
                // Send response
                self::formLimitHasBeenReached($event, true);
            }
        }
    }

    /**
     * On Registration Controller After Register
     *
     * @param FormEvent $event
     */
    public static function onRegistrationControllerAfterRegister($event)
    {
        /**
         * Save selected subscription plan in User preferences
         */
        /** @var RegistrationForm $form */
        $form = $event->getForm();
        /** @var User $user */
        $user = User::find()->where(['email' => $form->email])->one();
        $preferences = new UserPreferences();
        $preferences->set('Subscription.registration.subscriptionPlan', $form->subscription_plan);
        $user->preferences = Json::encode($preferences->getPreferences());
        $user->save(false);
    }

    /**
     * On Registration Controller After Confirmation
     *
     * @param UserEvent $event
     */
    public static function onRegistrationControllerAfterConfirmation($event)
    {
        /**
         * Redirect to Confirm a Subscription Plan
         * Check this is a new user with interest in a specific subscription plan
         */
        if ($subscriptionPlan = Yii::$app->user->preferences->get('Subscription.registration.subscriptionPlan')) {
            /** @var User $user */
            $user = $event->getUser();
            // Reset user preferences
            $user->preferences = null;
            $user->save(false);
            // Redirect to the selected subscription plan
            Yii::$app->controller->redirect(['/subscription/user/confirm', 'id' => $subscriptionPlan]);
            Yii::$app->end();
        }
    }

    /**
     * On Security Controller After Login
     *
     * @param FormEvent $event
     * @throws \Exception
     */
    public static function onSecurityControllerAfterLogin($event)
    {
        /** @var LoginForm $form */
        $form = $event->getForm();
        /** @var User $user */
        $user = $form->getUser();
        $settings = Yii::$app->settings;
        $auth = Yii::$app->authManager;

        /**
         * Check Trial Period
         */
        $defaultRole = $settings->get('app.defaultUserRole');
        $days = $settings->get('subscription.trialDays');
        if (!empty($defaultRole) && !empty($days) && is_numeric($days)) {
            $time = strtotime('-' . $days . ' day', time());
            if ($user->created_at < $time) {
                $roles = $auth->getRolesByUser($user->id);
                $roles = ArrayHelper::getColumn($roles, 'name');
                if (in_array($defaultRole, $roles)) {
                    // Remove default user role
                    $authRole = $auth->getRole($defaultRole);
                    $auth->revoke($authRole, $user->id);
                    // Disable forms
                    Form::updateAll(['status' => Form::OFF], ['created_by' => $user->id]);
                } elseif (empty($roles)) { // If user doesn't have any role
                    // Disable forms
                    Form::updateAll(['status' => Form::OFF], ['created_by' => $user->id]);
                }
            }
        }

        /**
         * Redirect to Confirm a Subscription Plan
         * Check this is a new user with interest in a specific subscription plan
         */
        if ($subscriptionPlan = Yii::$app->user->preferences->get('Subscription.registration.subscriptionPlan')) {
            // Reset user preferences
            $user->preferences = null;
            $user->save(false);
            // Redirect to the selected subscription plan
            Yii::$app->controller->redirect(['/subscription/user/confirm', 'id' => $subscriptionPlan]);
            Yii::$app->end();
        }

        /**
         * Downgrade User Roles
         * When Subscription has ended
         */

        /** @var Subscription $subscription */
        $subscription = Subscription::find()
            ->where(['user_id' => $user->id])
            ->andWhere(['not', ['ends_at' => null]])
            ->andWhere(['<', 'ends_at', time()])
            ->andWhere(['downgraded_at' => null])
            ->one();

        if ($subscription) {
            $subscription->downgradeUserRoles();
        }
    }

    /**
     * Event Handler
     * Before run a SideNav widget
     * Add link to Billing
     *
     * @param $event
     */
    public static function onSideNavBeforeRun($event)
    {
        if (isset($event->sender, $event->sender->params, $event->sender->params['id'])) {

            if ($event->sender->params['id'] === 'sidenav-account') {

                $sender = $event->sender;
                // Add link
                ArrayHelper::insert($sender->items, 1, [[
                    'url' => Url::to(['/subscription/user/index']),
                    'label' => Yii::t('app', 'Subscription'),
                    'icon' => 'credit-card',
                    'active' => isset($event->sender->params['moduleId'], $event->sender->params['controllerId'])
                        && $event->sender->params['moduleId'] === 'subscription'
                        && $event->sender->params['controllerId'] === 'user',
                    'visible' => true,
                ]], true);

                $event->sender = $sender;

            } elseif ($event->sender->params['id'] === 'sidenav-settings') {

                $sender = $event->sender;
                // Add link
                ArrayHelper::insert($sender->items, 1, [[
                    'url' => Url::to(['/subscription']),
                    'label' => Yii::t('app', 'Subscriptions'),
                    'icon' => 'wallet',
                    'active' => isset($event->sender->params['moduleId'], $event->sender->params['controllerId'])
                        && $event->sender->params['moduleId'] === 'subscription'
                        && $event->sender->params['controllerId'] === 'admin',
                    'visible' => true,
                ]], true);

                $event->sender = $sender;

            }
        }
    }

    /**
     * Form Limit Has Been Reached
     * Redirect Browser to Form Manager
     *
     * @param $event
     * @param bool $isAjaxRequest
     * @return mixed
     */
    public static function formLimitHasBeenReached($event, $isAjaxRequest = false)
    {
        // Set event as invalid
        $event->isValid = false;

        if ($isAjaxRequest) {
            // Send response
            /** @var Response $response */
            $response = Yii::$app->getResponse();
            // JSON by default
            $response->format = Response::FORMAT_JSON;
            $response->data = [
                'success' => false,
                'id'      => 0,
                'action'  => 'create',
                'message' => Yii::t('app', 'Form limit has been reached.'),
                'code'    => 403,
            ];
            $response->send();
            exit;
        }

        // Redirect to
        Yii::$app->getSession()->setFlash(
            'warning',
            Yii::t('app', 'You have reached the limit. To create more forms, {upgrade_subscription}.', [
                'upgrade_subscription' => Html::a(Yii::t('app', 'upgrade your subscription plan'), ['/subscription/user/index'], [
                    'style' => 'font-weight: bold; color: #9a5f15'
                ]),
            ])
        );

        return $event->action->controller->redirect(['/form/index']);
    }

    /**
     * Submission Limit Has Been Reached
     * Send response to browser
     *
     * @param $event
     */
    public static function submissionLimitHasBeenBeached($event)
    {
        // Set event as invalid
        $event->isValid = false;

        // Send response
        /** @var Response $response */
        $response = Yii::$app->getResponse();
        // JSON by default
        $response->format = Response::FORMAT_JSON;
        // Change response format
        $accept = $event->action->controller->request->headers->get('Accept');
        if (strpos($accept, 'application/xml') !== false) {
            $response->format = Response::FORMAT_XML;
        }
        $response->data = [
            'action'  => 'submit',
            'success' => false,
            'id' => 0,
            'message' => Yii::t('app', 'Submission limit has been reached.', [
                'startTag' => '<strong>',
                'endTag' => '</strong>',
            ]),
        ];
        $response->send();
        exit;
    }
}
