<?php namespace Common\Billing\Gateways\Paypal;

use App\Models\User;
use Common\Billing\Invoices\Invoice;
use Common\Billing\Models\Price;
use Common\Billing\Models\Product;
use Common\Billing\Notifications\NewInvoiceAvailable;
use Common\Billing\Subscription;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;

class PaypalSubscriptions
{
    use InteractsWithPaypalRestApi;

    public function isIncomplete(Subscription $subscription): bool
    {
        return $subscription->gateway_status === 'APPROVAL_PENDING' ||
            $subscription->gateway_status === 'APPROVED';
    }

    public function isPastDue(Subscription $subscription): bool
    {
        // no way to check this via PayPal API
        return false;
    }

    public function sync(
        string $paypalSubscriptionId,
        ?int $userId = null,
    ): void {
        $response = $this->paypal()->get(
            "billing/subscriptions/$paypalSubscriptionId",
        );

        $price = Price::where('paypal_id', $response['plan_id'])->firstOrFail();

        if ($userId != null) {
            $user = User::where('id', $userId)->firstOrFail();
            $user->update(['paypal_id' => $response['subscriber']['payer_id']]);
        } else {
            $user = User::where(
                'paypal_id',
                $response['subscriber']['payer_id'],
            )->firstOrFail();
        }

        $subscription = $user->subscriptions()->firstOrNew([
            'gateway_name' => 'paypal',
            'gateway_id' => $response['id'],
        ]);

        if (
            in_array($response['status'], ['CANCELLED', 'EXPIRED', 'SUSPENDED'])
        ) {
            $subscription->markAsCancelled();
        }

        $data = [
            'price_id' => $price->id,
            'product_id' => $price->product_id,
            'gateway_name' => 'paypal',
            'gateway_id' => $paypalSubscriptionId,
            'gateway_status' => $response['status'],
            'renews_at' =>
                $response['status'] === 'ACTIVE' &&
                isset($response['billing_info']['next_billing_time'])
                    ? Carbon::parse(
                        $response['billing_info']['next_billing_time'],
                    )
                    : null,
        ];

        if ($response['status'] === 'ACTIVE') {
            $data['ends_at'] = null;
        }

        $subscription->fill($data)->save();

        $this->createOrUpdateInvoice($subscription, $response->json());
    }

    public function createOrUpdateInvoice(
        Subscription $subscription,
        array $paypalSubscription,
    ): void {
        // subscription is no longer active, no need to update invoice
        if (!isset($paypalSubscription['billing_info']['next_billing_time'])) {
            return;
        }

        $startTime = Carbon::parse($paypalSubscription['start_time']);
        $renewsAt = Carbon::parse(
            $paypalSubscription['billing_info']['next_billing_time'],
        );
        $isPaid = $paypalSubscription['status'] === 'ACTIVE';

        $invoice = Invoice::whereBetween('created_at', [
            $startTime,
            $renewsAt,
        ])->first();
        if ($invoice) {
            // paid invoices should never be set to unpaid
            if (!$invoice->paid) {
                $invoice->update(['paid' => $isPaid]);
            }
        } else {
            $invoice = Invoice::create([
                'subscription_id' => $subscription->id,
                'paid' => $isPaid,
                'uuid' => Str::random(10),
            ]);
        }

        if ($invoice->paid && !$invoice->notified) {
            $subscription->user->notify(new NewInvoiceAvailable($invoice));
            $invoice->update(['notified' => true]);
        }
    }

    public function changePlan(
        Subscription $subscription,
        Product $newProduct,
        Price $newPrice,
    ): bool {
        $this->paypal()->post(
            "billing/subscriptions/$subscription->gateway_id/revise",
            [
                'plan_id' => $newPrice->paypal_id,
            ],
        );

        $this->sync($subscription->gateway_id);

        return true;
    }

    public function cancel(
        Subscription $subscription,
        $atPeriodEnd = true,
    ): bool {
        if ($atPeriodEnd) {
            $this->paypal()->post(
                "billing/subscriptions/$subscription->gateway_id/suspend",
                ['reason' => 'User requested cancellation.'],
            );
        } else {
            $this->paypal()->post(
                "billing/subscriptions/$subscription->gateway_id/cancel",
                ['reason' => 'Subscription deleted locally.'],
            );
        }

        $this->sync($subscription->gateway_id);

        return true;
    }

    public function resume(Subscription $subscription, array $params): bool
    {
        $this->paypal()->post(
            "billing/subscriptions/$subscription->gateway_id/activate",
            ['reason' => 'Subscription resumed by user.'],
        );

        $this->sync($subscription->gateway_id);

        return true;
    }
}
