<?php

namespace App\Services;

use App\Enums\CreditLedgerEntryType;
use App\Models\CreditLedgerEntry;
use App\Models\CreditProduct;
use App\Models\User;
use Illuminate\Support\Facades\DB;

class AiCreditService
{
    public function consume(User $user, int $credits = 1, ?string $source = null): bool
    {
        return DB::transaction(function () use ($credits, $source, $user): bool {
            $lockedUser = User::query()
                ->whereKey($user->getKey())
                ->lockForUpdate()
                ->firstOrFail();

            if ($lockedUser->ai_credits_balance < $credits) {
                return false;
            }

            $lockedUser->decrement('ai_credits_balance', $credits);
            $lockedUser->refresh();

            $this->record(
                user: $lockedUser,
                type: CreditLedgerEntryType::USAGE,
                credits: -$credits,
                source: $source,
            );

            $user->forceFill([
                'ai_credits_balance' => $lockedUser->ai_credits_balance,
            ]);

            return true;
        });
    }

    public function refund(User $user, int $credits = 1, ?string $source = null): CreditLedgerEntry
    {
        return $this->grant(
            user: $user,
            credits: $credits,
            type: CreditLedgerEntryType::REFUND,
            source: $source,
        );
    }

    /**
     * @param  array<string, mixed>|null  $metadata
     */
    public function grant(
        User $user,
        int $credits,
        CreditLedgerEntryType $type,
        ?CreditProduct $product = null,
        ?string $source = null,
        ?string $stripeCheckoutSessionId = null,
        ?string $stripeInvoiceId = null,
        ?string $stripePaymentIntentId = null,
        ?string $stripeSubscriptionId = null,
        ?array $metadata = null,
    ): ?CreditLedgerEntry {
        return DB::transaction(function () use (
            $credits,
            $metadata,
            $product,
            $source,
            $stripeCheckoutSessionId,
            $stripeInvoiceId,
            $stripePaymentIntentId,
            $stripeSubscriptionId,
            $type,
            $user,
        ): ?CreditLedgerEntry {
            if ($source && CreditLedgerEntry::query()->where('source', $source)->exists()) {
                return null;
            }

            $lockedUser = User::query()
                ->whereKey($user->getKey())
                ->lockForUpdate()
                ->firstOrFail();

            $lockedUser->increment('ai_credits_balance', $credits);
            $lockedUser->refresh();

            $entry = $this->record(
                user: $lockedUser,
                type: $type,
                credits: $credits,
                product: $product,
                source: $source,
                stripeCheckoutSessionId: $stripeCheckoutSessionId,
                stripeInvoiceId: $stripeInvoiceId,
                stripePaymentIntentId: $stripePaymentIntentId,
                stripeSubscriptionId: $stripeSubscriptionId,
                metadata: $metadata,
            );

            $user->forceFill([
                'ai_credits_balance' => $lockedUser->ai_credits_balance,
            ]);

            return $entry;
        });
    }

    /**
     * @param  array<string, mixed>|null  $metadata
     */
    private function record(
        User $user,
        CreditLedgerEntryType $type,
        int $credits,
        ?CreditProduct $product = null,
        ?string $source = null,
        ?string $stripeCheckoutSessionId = null,
        ?string $stripeInvoiceId = null,
        ?string $stripePaymentIntentId = null,
        ?string $stripeSubscriptionId = null,
        ?array $metadata = null,
    ): CreditLedgerEntry {
        return CreditLedgerEntry::create([
            'user_id' => $user->getKey(),
            'credit_product_id' => $product?->getKey(),
            'type' => $type,
            'credits' => $credits,
            'balance_after' => $user->ai_credits_balance,
            'source' => $source,
            'stripe_checkout_session_id' => $stripeCheckoutSessionId,
            'stripe_invoice_id' => $stripeInvoiceId,
            'stripe_payment_intent_id' => $stripePaymentIntentId,
            'stripe_subscription_id' => $stripeSubscriptionId,
            'metadata' => $metadata,
        ]);
    }
}
