<?php

namespace App\Services;

use App\Models\Family;
use App\Models\SubscriptionPaymentMethod;
use App\Services\Billing\BillingManager;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;

class SubscriptionPaymentMethodService
{
    public function __construct(
        private BillingManager $billingManager,
        private SubscriptionService $subscriptionService,
        private ActivityLogger $activityLogger
    ) {
    }

    public function list(Family $family, int $perPage = 15)
    {
        return $family->paymentMethods()->latest()->paginate($perPage);
    }

    public function store(Family $family, array $data, Request $request): SubscriptionPaymentMethod
    {
        $driver = Arr::get($data, 'driver') ?? config('billing.default_driver', 'null');
        $provider = $this->billingManager->driver($driver);
        $customer = $this->subscriptionService->ensureCustomer($family, $request, $driver);

        $email = Arr::get($data, 'email') ?? $family->owner?->email;

        if (!$email) {
            throw new InvalidArgumentException('Email address is required to create a payment method.');
        }

        $methodPayload = $provider->createPaymentMethod(array_merge($data, [
            'customer' => $customer,
            'email' => $email,
        ]));

        $reference = Arr::get($methodPayload, 'reference');

        if (!$reference) {
            throw new InvalidArgumentException('Billing provider did not return a payment method reference.');
        }

        $expiresAt = $this->resolveExpiry($methodPayload);

        $paymentMethod = DB::transaction(function () use ($family, $driver, $reference, $methodPayload, $expiresAt) {
            return $family->paymentMethods()->updateOrCreate(
                [
                    'provider' => $driver,
                    'reference' => $reference,
                ],
                [
                    'brand' => Arr::get($methodPayload, 'brand'),
                    'last_four' => Arr::get($methodPayload, 'last_four'),
                    'expires_at' => $expiresAt,
                    'meta' => Arr::get($methodPayload, 'meta', $methodPayload),
                ]
            );
        });

        $shouldSetDefault = (bool) Arr::get($data, 'set_default', false)
            || !$family->paymentMethods()->where('is_default', true)->exists();

        if ($shouldSetDefault) {
            $paymentMethod = $this->setDefault($family, $paymentMethod, $request);
        }

        $this->activityLogger->log('subscription.payment_method_saved', $paymentMethod, [
            'provider' => $paymentMethod->provider,
        ], $request, $family->id);

        return $paymentMethod;
    }

    public function setDefault(Family $family, SubscriptionPaymentMethod $paymentMethod, Request $request): SubscriptionPaymentMethod
    {
        $this->assertBelongsToFamily($family, $paymentMethod);

        $provider = $this->billingManager->driver($paymentMethod->provider);
        $customer = $this->subscriptionService->ensureCustomer($family, $request, $paymentMethod->provider);

        $updated = DB::transaction(function () use ($family, $paymentMethod) {
            $family->paymentMethods()->where('id', '!=', $paymentMethod->id)->update(['is_default' => false]);

            $paymentMethod->forceFill(['is_default' => true])->save();

            $settings = $family->settings ?? [];
            Arr::set($settings, 'billing.default_payment_method_id', $paymentMethod->id);
            Arr::set($settings, 'billing.default_payment_method_reference', $paymentMethod->reference);
            $family->update(['settings' => $settings]);

            return $paymentMethod->fresh();
        });

        $provider->attachPaymentMethod($customer, [
            'reference' => $updated->reference,
            'meta' => $updated->meta,
        ]);

        $this->activityLogger->log('subscription.payment_method_defaulted', $updated, [
            'provider' => $updated->provider,
        ], $request, $family->id);

        return $updated;
    }

    public function delete(Family $family, SubscriptionPaymentMethod $paymentMethod, Request $request): void
    {
        $this->assertBelongsToFamily($family, $paymentMethod);

        $wasDefault = $paymentMethod->is_default;

        DB::transaction(function () use ($family, $paymentMethod) {
            $paymentMethod->delete();

            $settings = $family->settings ?? [];
            if (Arr::get($settings, 'billing.default_payment_method_id') === $paymentMethod->id) {
                Arr::forget($settings, 'billing.default_payment_method_id');
                Arr::forget($settings, 'billing.default_payment_method_reference');
                $family->update(['settings' => $settings]);
            }
        });

        if ($wasDefault) {
            $nextMethod = $family->paymentMethods()->first();
            if ($nextMethod) {
                $this->setDefault($family, $nextMethod, $request);
            }
        }

        $this->activityLogger->log('subscription.payment_method_removed', $paymentMethod, [
            'provider' => $paymentMethod->provider,
        ], $request, $family->id);
    }

    private function resolveExpiry(array $methodPayload): ?Carbon
    {
        $expiresAt = Arr::get($methodPayload, 'expires_at');
        if ($expiresAt) {
            return Carbon::parse($expiresAt);
        }

        $year = Arr::get($methodPayload, 'exp_year');
        $month = Arr::get($methodPayload, 'exp_month');

        if ($year && $month) {
            return Carbon::createFromDate($year, $month, 1)->endOfMonth();
        }

        return null;
    }

    private function assertBelongsToFamily(Family $family, SubscriptionPaymentMethod $paymentMethod): void
    {
        if ($paymentMethod->subscriber_type !== Family::class || (int) $paymentMethod->subscriber_id !== (int) $family->id) {
            throw new InvalidArgumentException('Payment method does not belong to this family.');
        }
    }
}
