<?php


namespace App\Repositories;


use App\Company;
use App\Notifications\PaymentActionRequiredNotification;
use App\Notifications\PaymentFailedNotification;
use App\Notifications\UnexpectedCompanyDeletedFromStripeNotification;
use App\Notifications\UnexpectedUserDeletedFromStripeNotification;
use App\StripeUser;
use App\Subscription;
use App\Extensions\CustomSubscriptionBuilder;
use App\User;
use App\UserSubscription;
use Carbon\Carbon;
use Laravel\Cashier\Cashier;
use Laravel\Cashier\Payment;
use Stripe\PaymentIntent as StripePaymentIntent;

class StripeRepository {

    /******************************************************************************
     * **********************                                  ********************
     * **********************     API CONTROLLER FUNCTIONS     ********************
     * **********************                                  ********************
     ******************************************************************************/
    //Aquests son els metodes que sincronitzaran les accions de la nostra API a Stripe

    /**
     * Aquesta funció només s'utilitzarà quan
     *      1. Una companyia es vulgui subscriure per primera vegada
     *      2. Una companyia vulgui canviar de pla
     *
     * Nota: Quan una companyia vulgui acabar un pla que utilitzi cancelCompanySubscription()
     */
    public static function payCompanySubscription(Company $company, Subscription $subscription, bool $override) {
        if(!$company->hasPaymentMethod()) throw new \Exception("Please set a new payment method.");
        $payment_method = $company->stripe_payment_method_id;
        $stripe_plan_id = $subscription->plan->stripe_id;

        if($company->active_plan != null) {
            //Està subscrit ja
            if($company->active_plan->stripe_plan == $stripe_plan_id) {
                //Està subscrit i vol continuar en el mateix pla
                throw new \Exception("La renovación la hará Stripe via Webhook. Utiliza este método solo si es la primera vez, o si quieres cambiar de plan.");
            }
            else {
                //Està subscrit però vol canviar de pla
                if($override) {
                    //Està subscrit, vol canviar de pla i vol canviar ja
                    $company->active_plan->cancelNow();
                    (new CustomSubscriptionBuilder($company, "default", $stripe_plan_id))
                        ->create($payment_method, [
                            "subscription" => $subscription,
                            "subscription_id" => $subscription->id
                        ]);
                }
                else {
                    //Està subscrit, canviar de pla i vol canviar al final d'aquest periode
                    $company->active_plan->cancel();
                    (new CustomSubscriptionBuilder($company, "default", $stripe_plan_id))
                        ->trialUntil($company->active_plan->end_date) //es la manera d'aplaçar el pago
                        ->create($payment_method, [
                            "subscription" => $subscription,
                            "subscription_id" => $subscription->id
                        ]);
                }
            }
        }
        else {
            //És la primera vegada que es subscriu
            (new CustomSubscriptionBuilder($company, "default", $stripe_plan_id))
                ->create($payment_method, [
                    "subscription" => $subscription,
                    "subscription_id" => $subscription->id
                ]);
        }
    }


    public static function cancelCompanySubscription(Company $company, Subscription $subscription) {
        $company->subscription("default")->cancel();
        //$subscription->end_date;
    }

    public static function payUserSubscription(StripeUser $stripeUser, UserSubscription $userSubscriptionToPay, Carbon $start_date = null) {
        //si ja esta suscrit a aquesta empresa - l'start date per el trial, si no es passa es considerara override i començara ja
        if(!$stripeUser->hasPaymentMethod()) throw new \Exception("Please set a new payment method.");
        $payment_method_id = $stripeUser->stripe_payment_method_id;
        $stripe_plan_id = $userSubscriptionToPay->pricing->stripe_id;
        if($stripe_plan_id==null) throw new \Exception("La tarifa no está asociada a una tarifa de Stripe.");

        if($start_date!=null) {
            //no hi ha override, es cancelara quan acabi i la nova iniciara mes endavant, trial
            //ja s'ha fet a la primera peticio - $stripeUser->subscription('default')->cancel();
            (new CustomSubscriptionBuilder($stripeUser, "default", $stripe_plan_id))
                ->trialUntil($start_date) //es la manera d'aplaçar el pago
                ->create($payment_method_id, [
                    "subscription" => $userSubscriptionToPay,
                    "user_subscription_id" => $userSubscriptionToPay->id
                ]);
        }
        else {
            //hi ha override, es cancela ara l'actual i la nova inicia ara
            //ja s'ha fet a la primera peticio - $stripeUser->subscription('default')->cancelNow();
            (new CustomSubscriptionBuilder($stripeUser, "default", $stripe_plan_id))
                ->create($payment_method_id, [
                    "subscription" => $userSubscriptionToPay,
                    "user_subscription_id" => $userSubscriptionToPay->id
                ]);
        }

    }


    /******************************************************************************
     * **********************                                  ********************
     * **********************   WEBHOOK CONTROLLER FUNCTIONS   ********************
     * **********************                                  ********************
     ******************************************************************************/
    //Aquests son els metodes que sincronitzaran les accions de Stripe a la nostra BBDD

    public static function fromWebhookUpdateCompanyPaymentMethod($company_id, $setupIntent) {
        $payment_method_id = $setupIntent["payment_method"];
        $company = CompanyRepository::find($company_id);
        $company->updateDefaultPaymentMethod($payment_method_id);
        $company->stripe_payment_method_id = $payment_method_id;
        $company->save();
        $company->refresh();
        return $company;
    }

    public static function fromWebhookUpdateStripeUserPaymentMethod($stripe_user_id, $setupIntent) {
        $stripe_user = StripeUser::findOrFail($stripe_user_id);
        $payment_method_id = $setupIntent["payment_method"];
        $stripe_user->updateDefaultPaymentMethod($payment_method_id);
        $stripe_user->stripe_payment_method_id = $payment_method_id;
        $stripe_user->save();
        $stripe_user->refresh();
        return $stripe_user;
    }

    public static function fromWebhookSubscriptionRenewed($data) {
        //vol dir que s'ha pagat, s'ha renovat una subscripció
        $subscription = Subscription::where("stripe_id", $data["subscription"])->first();
        $user_subscription = UserSubscription::where("stripe_id", $data["subscription"])->first();

        if($subscription != null) { //es una subscripcio
            SubscriptionRepository::pay($subscription->id, false, true); //with webhook flag activated
        }
        elseif($user_subscription != null) { //es una user subscription
            UserSubscriptionRepository::payUserSubscription($user_subscription->id, "stripe", null, null, true);
        }
        else throw new \Exception("Subscription not found. Provide user_subscription_id or subscription_id inside metadata.");
    }

    public static function fromWebhookSubscriptionPaymentFailed($data) {
        //1. Trobar la subscripcio
        $subscription = Subscription::where("stripe_id", $data["subscription"])->first();
        $user_subscription = UserSubscription::where("stripe_id", $data["subscription"])->first();
        if($subscription != null) { //es una subscripcio
            $notifiable = CompanyRepository::find($subscription->company_id);
        }
        elseif($user_subscription != null) { //es una user subscription
            $notifiable = UserRepository::find($subscription->user_id);
            if($notifiable->one_signal_player_id!=null) {
                $params = [
                    "title" => "¡Ha habido un error durante la paga de tu subscripción, por favor, contacta con tu box",
                    "user_id" => $notifiable->id
                ];
                NotificationRepository::sendNotification($params);
            }
        }
        else throw new \Exception("Subscription not found. Provide user_subscription_id or subscription_id inside metadata.");

        $notifiable->notify((new PaymentFailedNotification($notifiable, ($subscription ? $subscription : $user_subscription)))->delay(Carbon::now()->addSeconds(5)));
    }

    public static function fromWebhookSubscriptionPaymentActionRequired($data) {
        //1. Trobar la subscripcio
        $subscription = Subscription::where("stripe_id", $data["subscription"])->first();
        $user_subscription = UserSubscription::where("stripe_id", $data["subscription"])->first();
        if($subscription != null) { //es una subscripcio
            $notifiable = CompanyRepository::find($subscription->company_id);
        }
        elseif($user_subscription != null) { //es una user subscription
            $notifiable = UserRepository::find($subscription->user_id);
        }
        else throw new \Exception("Subscription not found. Provide user_subscription_id or subscription_id inside metadata.");

        //1. Creem pagament intent
        $options = StripeRepository::stripeOptions($user_subscription ? $user_subscription->pricing->company_id : null );
        $payment = new Payment(StripePaymentIntent::retrieve( $data['payment_intent'], $options ));

        //2. Notifiquem
        $notifiable->notify((new PaymentActionRequiredNotification($notifiable, $payment, $options["api_key"]))->delay(Carbon::now()->addSeconds(5)));
    }

    public static function fromWebhookSubscriptionDeleted($data) {
        $cancelNow = !$data["cancel_at_period_end"];
        $cancelAt = $data["cancel_at"];
        if(isset($data["metadata"]["subscription_id"])) {
            $sub = SubscriptionRepository::find($data["metadata"]["subscription_id"]);
            $sub->end_date = $cancelNow ? Carbon::now() : ($cancelAt!=null ? Carbon::createFromTimestamp($cancelAt) : $sub->end_date);
            $sub->save();
        }
        elseif(isset($data["metadata"]["user_subscription_id"])) {
            //es un user
            $sub = UserSubscriptionRepository::find($data["metadata"]["user_subscription_id"]);
            $sub->end_date = $cancelNow ? Carbon::now() : ($cancelAt!=null ? Carbon::createFromTimestamp($cancelAt) : $sub->end_date);
            $sub->save();
        }
        else throw new \Exception("Customer not found. Provide user_subscription_id or subscription_id inside metadata.");
    }

    public static function fromWebhookCustomerDeleted($data) {
        if(isset($data["metadata"]["company_id"])) {
            //es un company
            //avisem a la companyia
            $company = CompanyRepository::find($data["metadata"]["company_id"]);
            $company->notify((new UnexpectedCompanyDeletedFromStripeNotification($company, $company))->delay(Carbon::now()->addSeconds(5)));

            //avisem a tots els super admins
            foreach(UserRepository::superUsers() as $u) {
                $u->notify((new UnexpectedCompanyDeletedFromStripeNotification($u, $company))->delay(Carbon::now()->addSeconds(5)));
            }
        }
        elseif(isset($data["metadata"]["stripe_user_id"])) {
            //es un user
            $user = StripeUser::find($data["metadata"]["stripe_user_id"])->user;
            $user->notify((new UnexpectedUserDeletedFromStripeNotification($user))->delay(Carbon::now()->addSeconds(5)));
        }
        else throw new \Exception("Customer not found. Provide stripe_user_id or company_id inside metadata.");
    }

    /******************************************************************************
     * **********************                                  ********************
     * **********************          UTILS FUNCTIONS         ********************
     * **********************                                  ********************
     ******************************************************************************/
    //Aquests son metodes d'ajuda

    public static function companyToStripeFormat(Company $c) {
        return  [
            "address" => [
                "line1" => $c->fact_address_line1,
                "line2" => $c->fact_address_line2,
                "country" => $c->fact_country_code,
                "city" => $c->fact_city,
                "postal_code" => $c->fact_zipcode,
                "state" => $c->fact_county,
            ],
            "description" => $c->slogan,
            "email" => $c->email,
            "name" => $c->fact_name,
            "phone" => $c->phone,
            "metadata" => [
                "company_id" => $c->id
            ],
            //"invoice_prefix" => $c->fact_prefix,
            "preferred_locales" => ["es"],
            "tax_exempt" => "none",
            /*"tax_id_data" => [
                "type" => "es_cif",
                "value" => $c->cif
            ]*/
        ];
    }

    public static function stripeToCompanyFormat($stripe_data_object) {
        return  [
            "fact_address_line1" => $stripe_data_object["address"]["line1"],
            "fact_address_line2" => $stripe_data_object["address"]["line2"],
            "fact_country_code" => $stripe_data_object["address"]["country"],
            "fact_city" => $stripe_data_object["address"]["city"],
            "fact_zipcode" => $stripe_data_object["address"]["postal_code"],
            "fact_county" => $stripe_data_object["address"]["state"],
            "slogan" => $stripe_data_object["description"],
            "email" => $stripe_data_object["email"],
            "phone" => $stripe_data_object["phone"],
            "fact_name" => $stripe_data_object["name"]
        ];
    }

    public static function stripeOptions($company_id = null, array $options = []) {
        if($company_id != null) {
            $key = CompanyRepository::find($company_id)->stripe_private_key;
            if(!$key) throw new \Exception("The company does not have a Stripe private key set.");
        }
        else {
            $key = config('cashier.secret');
        }

        return array_merge([
            'api_key' => $key,
            'stripe_version' => Cashier::STRIPE_VERSION,
        ], $options);
    }

    public static function userToStripeFormat(StripeUser $su, User $u) {
        return  [
            "address" => [
                "line1" => $u->address,
                "line2" => null,
                "country" => $u->country,
                "city" => $u->poblacio,
                "postal_code" => $u->zipcode,
            ],
            "email" => $u->email,
            "name" => $u->name . " " . $u->surnames,
            "phone" => $u->phone,
            "metadata" => [
                "user_id" => $u->id,
                "stripe_user_id" => $su->id
            ],
            //"invoice_prefix" => $c->fact_prefix,
            "preferred_locales" => ["es"],
            "tax_exempt" => "none",
            /*"tax_id_data" => [
                "type" => "es_cif",
                "value" => $c->cif
            ]*/
        ];
    }


    /******************************************************************************
     * **********************                                  ********************
     * **********************       STRIPE USER FUNCTIONS      ********************
     * **********************                                  ********************
     ******************************************************************************/
    public static function createStripeUserForCompany(Company $c, User $u) {
        //es suposa que ja s'ha comprovat que no existeix
        //creem usuari aqui
        $stripe_user = StripeUser::create(["user_id" => $u->id, "company_id" => $c->id]);
        $stripe_user->createAsStripeCustomer(StripeRepository::userToStripeFormat($stripe_user, $u));
        return $stripe_user;
    }

}
