<?php

namespace App\Modules\Admin\Tenancy\Services;

use App\Modules\Admin\Billing\Repositories\InvoiceRepository;
use App\Modules\Admin\Billing\Services\BillingAuditService;
use App\Modules\Admin\Tenancy\Events\TenantSubscriptionAffected;
use App\Modules\Admin\Tenancy\Models\Plan;
use App\Modules\Admin\Tenancy\Models\Subscription;
use App\Modules\Admin\Tenancy\Repositories\SubscriptionRepository;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Event;

class SubscriptionLifecycleService
{
    public function __construct(
        private readonly SubscriptionRepository $subscriptions,
        private readonly BillingAuditService $billingAudit,
        private readonly TenantLifecycleService $tenantLifecycle,
        private readonly InvoiceRepository $invoices,
    ) {}

    public function runScheduledTransitions(): int
    {
        $changed = 0;
        $changed += $this->moveTrialsToGrace();
        $changed += $this->moveActivesToGrace();
        $changed += $this->moveGraceToExpired();

        return $changed;
    }

    public function renewPaidSubscription(Subscription $subscription, Plan $plan): Subscription
    {
        $endsAt = $this->nextPaidPeriodEnd($plan);
        $this->subscriptions->updateModel($subscription, [
            'status' => Subscription::STATUS_ACTIVE,
            'ends_at' => $endsAt,
            'grace_ends_at' => null,
            'trial_ends_at' => null,
        ]);
        $subscription->refresh();
        $subscription->load(['tenant', 'plan']);
        if ($subscription->tenant_id) {
            $this->billingAudit->logSystem((int) $subscription->tenant_id, 'subscription.renewed', [
                'subscription_id' => (int) $subscription->id,
            ]);
            $this->tenantLifecycle->syncActiveFromSubscription((int) $subscription->tenant_id);
            $this->invoices->createDraftForSubscription($subscription);
        }
        if ($subscription->tenant) {
            Event::dispatch(new TenantSubscriptionAffected($subscription->tenant));
        }

        return $subscription;
    }

    private function nextPaidPeriodEnd(Plan $plan): ?Carbon
    {
        return match ($plan->billing_cycle) {
            'monthly' => now()->addMonth(),
            'yearly' => now()->addYear(),
            default => null,
        };
    }

    private function moveTrialsToGrace(): int
    {
        $n = 0;
        $days = (int) config('subscription.grace_days_after_trial', 7);
        $this->subscriptions->trialEndedChunked(function (Subscription $sub) use ($days, &$n): void {
            $this->subscriptions->updateModel($sub, [
                'status' => Subscription::STATUS_GRACE_PERIOD,
                'grace_ends_at' => now()->addDays($days),
            ]);
            $this->billingAudit->logSystem((int) $sub->tenant_id, 'subscription.trial_to_grace', [
                'subscription_id' => (int) $sub->id,
            ]);
            $this->tenantLifecycle->syncGraceFromSubscription((int) $sub->tenant_id);
            $this->notifyTenant($sub);
            $n++;
        });

        return $n;
    }

    private function moveActivesToGrace(): int
    {
        $n = 0;
        $days = (int) config('subscription.grace_days_after_paid_period', 7);
        $this->subscriptions->activePaidEndedChunked(function (Subscription $sub) use ($days, &$n): void {
            $this->subscriptions->updateModel($sub, [
                'status' => Subscription::STATUS_GRACE_PERIOD,
                'grace_ends_at' => now()->addDays($days),
            ]);
            $this->billingAudit->logSystem((int) $sub->tenant_id, 'subscription.active_to_grace', [
                'subscription_id' => (int) $sub->id,
            ]);
            $this->tenantLifecycle->syncGraceFromSubscription((int) $sub->tenant_id);
            $this->notifyTenant($sub);
            $n++;
        });

        return $n;
    }

    private function moveGraceToExpired(): int
    {
        $n = 0;
        $this->subscriptions->graceEndedChunked(function (Subscription $sub) use (&$n): void {
            $this->subscriptions->updateModel($sub, [
                'status' => Subscription::STATUS_EXPIRED,
                'ends_at' => now(),
            ]);
            $this->billingAudit->logSystem((int) $sub->tenant_id, 'subscription.grace_to_expired', [
                'subscription_id' => (int) $sub->id,
            ]);
            $this->tenantLifecycle->syncActiveFromSubscription((int) $sub->tenant_id);
            $this->notifyTenant($sub);
            $n++;
        });

        return $n;
    }

    private function notifyTenant(Subscription $sub): void
    {
        $sub->loadMissing('tenant');
        if ($sub->tenant) {
            Event::dispatch(new TenantSubscriptionAffected($sub->tenant));
        }
    }
}
