<?php

declare(strict_types=1);

namespace App\Services\Billing;

use App\Models\AccountCredit;
use App\Models\CreditNote;
use App\Models\DebitNote;
use App\Models\Invoice;
use App\Models\PaymentTransaction;
use App\Models\User;
use Illuminate\Support\Facades\DB;

class CreditService
{
    /**
     * Issue a credit note and add funds to the customer's account credit balance.
     */
    public function issueCreditNote(
        User $user,
        float $amount,
        ?string $reason = null,
        ?Invoice $invoice = null,
        ?User $creator = null,
    ): CreditNote {
        return DB::transaction(function () use ($user, $amount, $reason, $invoice, $creator): CreditNote {
            $creditNote = CreditNote::create([
                'user_id' => $user->id,
                'invoice_id' => $invoice?->id,
                'number' => CreditNote::generateNumber(),
                'amount' => $amount,
                'currency' => 'USD',
                'reason' => $reason,
                'status' => 'issued',
                'issued_at' => now(),
                'created_by' => $creator?->id,
            ]);

            AccountCredit::create([
                'user_id' => $user->id,
                'amount' => $amount,
                'currency' => 'USD',
                'type' => 'credit_note',
                'description' => "Credit note {$creditNote->number}".($reason ? ": {$reason}" : ''),
                'reference_type' => CreditNote::class,
                'reference_id' => $creditNote->id,
                'created_by' => $creator?->id,
            ]);

            $user->increment('credit_balance', $amount);

            return $creditNote;
        });
    }

    /**
     * Issue a debit note to reduce the customer's credit balance.
     * If balance is insufficient, reduces to 0 (does not go negative).
     */
    public function issueDebitNote(
        User $user,
        float $amount,
        string $reasonType,
        ?string $reason = null,
        ?Invoice $invoice = null,
        ?User $creator = null,
    ): DebitNote {
        return DB::transaction(function () use ($user, $amount, $reasonType, $reason, $invoice, $creator): DebitNote {
            $debitNote = DebitNote::create([
                'user_id' => $user->id,
                'invoice_id' => $invoice?->id,
                'number' => DebitNote::generateNumber(),
                'amount' => $amount,
                'currency' => 'USD',
                'reason_type' => $reasonType,
                'reason' => $reason,
                'status' => 'issued',
                'issued_at' => now(),
                'created_by' => $creator?->id,
            ]);

            // Deduct from balance, but don't go below 0
            $user->refresh();
            $deduction = min((float) $user->credit_balance, $amount);

            if ($deduction > 0) {
                $user->decrement('credit_balance', $deduction);

                AccountCredit::create([
                    'user_id' => $user->id,
                    'amount' => -$deduction,
                    'currency' => 'USD',
                    'type' => 'admin_adjustment',
                    'description' => "Debit note {$debitNote->number}".($reason ? ": {$reason}" : ''),
                    'reference_type' => DebitNote::class,
                    'reference_id' => $debitNote->id,
                    'created_by' => $creator?->id,
                ]);
            }

            return $debitNote;
        });
    }

    /**
     * Manually adjust a customer's credit balance (admin adjustment).
     */
    public function adjustBalance(
        User $user,
        float $amount,
        string $description,
        ?User $creator = null,
    ): AccountCredit {
        return DB::transaction(function () use ($user, $amount, $description, $creator): AccountCredit {
            $credit = AccountCredit::create([
                'user_id' => $user->id,
                'amount' => $amount,
                'currency' => 'USD',
                'type' => 'admin_adjustment',
                'description' => $description,
                'created_by' => $creator?->id,
            ]);

            if ($amount >= 0) {
                $user->increment('credit_balance', $amount);
            } else {
                $user->decrement('credit_balance', abs($amount));
            }

            return $credit;
        });
    }

    /**
     * Apply account credits to an invoice during checkout/payment.
     * Returns the amount of credit applied.
     */
    public function applyCreditsToInvoice(User $user, Invoice $invoice): float
    {
        return DB::transaction(function () use ($user, $invoice): float {
            $user->refresh();
            $balance = (float) $user->credit_balance;

            if ($balance <= 0) {
                return 0.0;
            }

            $invoiceTotal = (float) $invoice->total;

            if ($invoiceTotal <= 0) {
                return 0.0;
            }

            $amountToApply = min($balance, $invoiceTotal);

            // Create a payment transaction recording the credit application
            PaymentTransaction::create([
                'user_id' => $user->id,
                'invoice_id' => $invoice->id,
                'gateway' => 'credit',
                'gateway_transaction_id' => null,
                'amount' => $amountToApply,
                'currency' => $invoice->currency ?? 'USD',
                'status' => 'succeeded',
                'payment_method' => 'account_credit',
                'description' => "Account credit applied to invoice {$invoice->number}",
            ]);

            AccountCredit::create([
                'user_id' => $user->id,
                'amount' => -$amountToApply,
                'currency' => 'USD',
                'type' => 'admin_adjustment',
                'description' => "Applied to invoice {$invoice->number}",
                'reference_type' => Invoice::class,
                'reference_id' => $invoice->id,
            ]);

            $user->decrement('credit_balance', $amountToApply);

            // If credit covers the full invoice, mark it as paid
            if ($amountToApply >= $invoiceTotal) {
                $invoice->update([
                    'status' => 'paid',
                    'paid_at' => now(),
                ]);
            }

            return $amountToApply;
        });
    }

    /**
     * Refund a payment transaction to account credit instead of the original payment method.
     */
    public function refundToCredit(
        User $user,
        PaymentTransaction $transaction,
        float $amount,
    ): AccountCredit {
        return DB::transaction(function () use ($user, $transaction, $amount): AccountCredit {
            $credit = AccountCredit::create([
                'user_id' => $user->id,
                'amount' => $amount,
                'currency' => $transaction->currency ?? 'USD',
                'type' => 'refund',
                'description' => "Refund from transaction #{$transaction->id}",
                'reference_type' => PaymentTransaction::class,
                'reference_id' => $transaction->id,
            ]);

            $user->increment('credit_balance', $amount);

            return $credit;
        });
    }

    /**
     * Void a credit note: reverse the credit and reduce the user's balance.
     */
    public function voidCreditNote(CreditNote $creditNote): void
    {
        DB::transaction(function () use ($creditNote): void {
            $creditNote->update(['status' => 'voided']);

            $user = $creditNote->user;
            $amount = (float) $creditNote->amount;

            // Deduct the amount back (to 0 minimum)
            $deduction = min((float) $user->credit_balance, $amount);

            if ($deduction > 0) {
                $user->decrement('credit_balance', $deduction);
            }

            AccountCredit::create([
                'user_id' => $user->id,
                'amount' => -$amount,
                'currency' => 'USD',
                'type' => 'admin_adjustment',
                'description' => "Voided credit note {$creditNote->number}",
                'reference_type' => CreditNote::class,
                'reference_id' => $creditNote->id,
            ]);
        });
    }

    /**
     * Void a debit note: restore the debited amount to the user's balance.
     */
    public function voidDebitNote(DebitNote $debitNote): void
    {
        DB::transaction(function () use ($debitNote): void {
            $debitNote->update(['status' => 'voided']);

            $user = $debitNote->user;
            $amount = (float) $debitNote->amount;

            $user->increment('credit_balance', $amount);

            AccountCredit::create([
                'user_id' => $user->id,
                'amount' => $amount,
                'currency' => 'USD',
                'type' => 'admin_adjustment',
                'description' => "Voided debit note {$debitNote->number}",
                'reference_type' => DebitNote::class,
                'reference_id' => $debitNote->id,
            ]);
        });
    }
}
