<?php

namespace Tobuli\Helpers\Payments\Gateways;


use App\Exceptions\PaymentsConfigurationException;
use App\Exceptions\PaymentsIssueException;
use App\Exceptions\PaymentsUnavailableException;
use Braintree\Transaction;
use Braintree_Configuration;
use Braintree_Gateway;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;


class BraintreeGateway extends PaymentGateway implements PaymentGatewayInterface
{
    const transactionSuccessStatuses = [
        Transaction::AUTHORIZED,
        Transaction::AUTHORIZING,
        Transaction::SETTLED,
        Transaction::SETTLING,
        Transaction::SETTLEMENT_CONFIRMED,
        Transaction::SETTLEMENT_PENDING,
        Transaction::SUBMITTED_FOR_SETTLEMENT,
    ];

    const ACTIVE = 'Active';

    private $config;
    private $gateway;

    public function __construct()
    {
        $this->config = settings('payments.braintree');
        $this->gateway = $this->setupService();
    }

    public function pay($user, $plan)
    {
        try {
            $result = $this->gateway->transaction()->sale([
                'amount'             => $plan->price,
                'paymentMethodNonce' => request('payment_method_nonce'),
                'options'            => [
                    'submitForSettlement' => true,
                ],
            ]);

            if ( ! $result->success || is_null($result->transaction))
                throw new PaymentsIssueException($result->message);
        } catch (\Exception $e) {
            throw new PaymentsUnavailableException();
        }

        $this->storeSubscription($user, $plan, $result->transaction->id);

        return Redirect::route('payments.pay_callback', [
            'gateway'        => $this->gatewayName(),
            'transaction_id' => $result->transaction->id,
        ]);
    }

    public function payCallback(Request $request)
    {
        try {
            $transaction = $this->gateway->transaction()->find($request->transaction_id);

            if ( ! in_array($transaction->status, self::transactionSuccessStatuses)) {
                throw new \Exception();
            }
        } catch (\Exception $exception) {
            throw new PaymentsUnavailableException();
        }

        $this->activateSubscription($request->transaction_id);

        return Redirect::route('payments.success');
    }

    public function subscribe($user, $plan)
    {
        try {
            $result = $this->gateway->paymentMethod()->create([
                'customerId'         => request('customer_id'),
                'paymentMethodNonce' => request('payment_method_nonce'),
            ]);

            if ( ! $result->success || is_null($token = $result->paymentMethod->token))
                throw new PaymentsIssueException($result->message);

            $result = $this->gateway->subscription()->create([
                'price'              => $plan->price,
                'trialPeriod'        => false,
                'paymentMethodToken' => $token,
                'planId'             => $this->config['plans'][$plan->id],
                'merchantAccountId'  => $this->config['merchant_account_id'],
                'options'            => [
                    'startImmediately' => true,
                ],
            ]);

            if ( ! $result->success)
                throw new PaymentsIssueException($result->message);

        } catch (\Exception $e) {
            throw new PaymentsUnavailableException();
        }

        $this->storeSubscription($user, $plan, $result->subscription->id);

        return Redirect::route('payments.subscribe_callback', [
            'gateway'         => $this->gatewayName(),
            'subscription_id' => $result->subscription->id,
        ]);
    }

    public function subscribeCallback(Request $request)
    {
        try {
            $subscription = $this->gateway->subscription()->find($request->subscription_id);

            if ($subscription->status != 'Active') {
                throw new \Exception('Subscription not active.');
            }

            if ( ! isset($subscription->transactions[0])) {
                throw new \Exception('Subscription does not contain transaction.');
            }

            if ( ! in_array($subscription->transactions[0]->status, self::transactionSuccessStatuses)) {
                throw new \Exception('Bad transaction status.');
            }
        } catch (\Exception $e) {
            throw new PaymentsUnavailableException();
        }

        $this->activateSubscription($request->subscription_id);

        return Redirect::route('payments.success');
    }

    public function checkout($plan_id)
    {
        try {
            $result = $this->gateway->customer()->create();

            if ( ! $result->success) throw new \Exception();

            $client_token = $this->gateway->clientToken()->generate([
                'customerId' => $result->customer->id,
            ]);
        } catch (\Exception $e) {
            throw new PaymentsUnavailableException();
        }

        return view('front::Subscriptions.Gateways.braintree')->with([
            'plan_id'     => $plan_id,
            'gateway'     => $this->gatewayName(),
            'token'       => $client_token,
            'customer_id' => $result->customer->id,
        ]);
    }

    public function isConfigCorrect(Request $request)
    {
        try {
            $this->buildConfig($request);

            $this->gateway = $this->setupService();

            $merchant = $this->gateway->merchantAccount()->find($this->config['merchant_account_id']);

            foreach ($this->gateway->plan()->all() as $plan) {
                if ($merchant->currencyIsoCode !== $plan->currencyIsoCode)
                    throw new \Exception(trans('admin.braintree_currencies_doesnt_match') . ' ' . $plan->id);
            }
        } catch (\Exception $e) {
            $message = $e->getMessage() ?
                ucfirst($e->getMessage()) : trans('front.login_failed');

            throw new PaymentsConfigurationException($message);
        }

        return true;
    }

    public function getPlanIds()
    {
        $braintree_plans_ids = [];

        foreach ($this->gateway->plan()->all() as $plan) {
            $braintree_plans_ids[$plan->id] = $plan->id;
        }

        return $braintree_plans_ids;
    }

    public function isSubscriptionActive($subscription)
    {
        try {
            $subscription = $this->gateway->subscription()->find($subscription->gateway_id);
        } catch (\Exception $exception) {
            return false;
        }

        if ($subscription->status != self::ACTIVE) {
            return false;
        }

        return true;
    }

    public function storeConfig($request, $gateway)
    {
        $this->buildConfig($request);

        settings('payments.' . $gateway, array_merge(settings('payments.' . $gateway), $this->config));
        settings('payments.gateways.' . $gateway, $request->active ? 1 : 0);
    }

    private function buildConfig($request)
    {
        $config = $request->except('_token', 'active', 'plan_ids', 'billing_plans');

        try {
            $config['plans'] = array_combine($request->billing_plans, $request->plan_ids);
        } catch (\Exception $e) {
            //throw new \Exception(trans('front.attribute_missing', ['attribute' => trans('validation.attributes.default_billing_plan')]));
        }

        $this->config = $config;
    }

    private function setupService()
    {
        return new Braintree_Gateway(
            new Braintree_Configuration(array_only($this->config, [
                'environment',
                'merchantId',
                'publicKey',
                'privateKey',
            ]))
        );
    }

    public function cancelSubscription($subscription)
    {
        try {
            $this->gateway->subscription()->cancel($subscription->gateway_id);
        } catch (\Exception $exception) {
            return false;
        }

        return true;
    }
}