<?php

namespace App\Services;

use App\Contracts\Billing\BillingProvider;
use App\Models\Family;
use App\Models\Plan;
use App\Models\Subscription;
use App\Services\Billing\BillingManager;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;

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

    public function preview(Family $family, Plan $plan, array $options = [], ?Request $request = null, ?string $driver = null): array
    {
        $provider = $this->resolveProvider($driver);
        $customer = $this->getOrCreateCustomer($family, $provider, $request);

        return $provider->previewPlanChange($customer, $plan, $options);
    }

    public function create(Family $family, Plan $plan, array $payload, Request $request, ?string $driver = null): Subscription
    {
        $provider = $this->resolveProvider($driver);

        return DB::transaction(function () use ($family, $plan, $payload, $request, $provider, $driver) {
            $customer = $this->getOrCreateCustomer($family, $provider, $request);

            $providerSubscription = $provider->createSubscription($customer, $plan, Arr::get($payload, 'options', []));

            $subscription = $family->subscriptions()->create([
                'plan_id' => $plan->id,
                'status' => $providerSubscription['status'] ?? 'active',
                'renews_at' => $this->calculateRenewsAt($plan),
                'meta' => [
                    'provider_payload' => $providerSubscription,
                    'customer' => $customer,
                    'options' => Arr::get($payload, 'options', []),
                ],
                'provider' => $driver ?? config('billing.default_driver', 'null'),
                'provider_subscription_id' => $providerSubscription['id'] ?? null,
            ]);

            // End previous active subscriptions
            $family->subscriptions()
                ->where('id', '!=', $subscription->id)
                ->whereNull('ends_at')
                ->whereIn('status', ['active', 'pending'])
                ->update([
                    'status' => 'superseded',
                    'ends_at' => now(),
                ]);

            $this->activityLogger->log('subscription.created', $subscription, [
                'plan_code' => $plan->code,
            ], $request, $family->id);

            return $subscription->fresh('plan');
        });
    }

    public function swap(Family $family, Subscription $subscription, Plan $plan, array $payload, Request $request, ?string $driver = null): Subscription
    {
        $provider = $this->resolveProvider($driver ?? $subscription->provider);

        $this->assertOwnership($family, $subscription);

        $providerSubscription = $provider->swapSubscription($this->buildProviderSubscription($subscription), $plan, Arr::get($payload, 'options', []));

        $subscription->update([
            'plan_id' => $plan->id,
            'status' => $providerSubscription['status'] ?? 'active',
            'renews_at' => $this->calculateRenewsAt($plan),
            'meta' => array_merge($subscription->meta ?? [], [
                'provider_payload' => $providerSubscription,
                'options' => Arr::get($payload, 'options', []),
            ]),
            'provider' => $driver ?? $subscription->provider,
            'provider_subscription_id' => $providerSubscription['id'] ?? $subscription->provider_subscription_id,
        ]);

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

        return $subscription->fresh('plan');
    }

    public function cancel(Family $family, Subscription $subscription, bool $immediate, Request $request, ?string $driver = null): Subscription
    {
        $provider = $this->resolveProvider($driver ?? $subscription->provider);

        $this->assertOwnership($family, $subscription);

        $provider->cancelSubscription($this->buildProviderSubscription($subscription), $immediate);

        $subscription->update([
            'status' => 'canceled',
            'ends_at' => $immediate ? now() : ($subscription->ends_at ?? now()),
        ]);

        $this->activityLogger->log('subscription.canceled', $subscription, [
            'immediate' => $immediate,
        ], $request, $family->id);

        return $subscription->fresh('plan');
    }

    public function resume(Family $family, Subscription $subscription, Request $request, ?string $driver = null): Subscription
    {
        $provider = $this->resolveProvider($driver ?? $subscription->provider);

        $this->assertOwnership($family, $subscription);

        $plan = $subscription->plan;

        if (!$plan) {
            throw new InvalidArgumentException('Subscription is missing an associated plan.');
        }

        $providerSubscription = $provider->createSubscription($this->buildProviderSubscription($subscription), $plan, []);

        $subscription->update([
            'status' => $providerSubscription['status'] ?? 'active',
            'ends_at' => null,
            'renews_at' => $this->calculateRenewsAt($plan),
            'provider_subscription_id' => $providerSubscription['id'] ?? $subscription->provider_subscription_id,
            'meta' => array_merge($subscription->meta ?? [], [
                'provider_payload' => $providerSubscription,
            ]),
        ]);

        $this->activityLogger->log('subscription.resumed', $subscription, [], $request, $family->id);

        return $subscription->fresh('plan');
    }

    public function listInvoices(Family $family, Subscription $subscription, int $perPage = 15)
    {
        $this->assertOwnership($family, $subscription);

        return $subscription->invoices()->latest()->paginate($perPage);
    }

    public function ensureCustomer(Family $family, ?Request $request = null, ?string $driver = null): array
    {
        $provider = $this->resolveProvider($driver);

        return $this->getOrCreateCustomer($family, $provider, $request);
    }

    private function resolveProvider(?string $driver): BillingProvider
    {
        return $this->billingManager->driver($driver);
    }

    private function getOrCreateCustomer(Family $family, BillingProvider $provider, ?Request $request = null): array
    {
        $settings = $family->settings ?? [];
        $billing = $settings['billing'] ?? [];

        if (!empty($billing['customer'])) {
            return $billing['customer'];
        }

        $payload = [
            'name' => $family->name,
            'email' => optional($family->owner)->email,
            'family_id' => $family->id,
        ];

        $customer = $provider->createCustomer($payload);

        $billing['customer'] = $customer;
        $settings['billing'] = $billing;

        $family->update(['settings' => $settings]);

        $this->activityLogger->log('billing.customer_created', $family, [
            'customer' => $customer,
        ], $request, $family->id);

        return $customer;
    }

    private function calculateRenewsAt(Plan $plan): ?Carbon
    {
        $now = now();
        return match (strtolower($plan->interval)) {
            'monthly' => $now->copy()->addMonth(),
            'yearly', 'annual', 'annually' => $now->copy()->addYear(),
            'weekly' => $now->copy()->addWeek(),
            'quarterly' => $now->copy()->addMonths(3),
            default => null,
        };
    }

    private function buildProviderSubscription(Subscription $subscription): array
    {
        return [
            'id' => $subscription->provider_subscription_id,
            'status' => $subscription->status,
            'meta' => $subscription->meta,
        ];
    }

    private function assertOwnership(Family $family, Subscription $subscription): void
    {
        if ($subscription->subscriber_type !== $family::class || (int) $subscription->subscriber_id !== (int) $family->id) {
            throw new InvalidArgumentException('Subscription does not belong to the specified family.');
        }
    }
}
