<?php

namespace App\Services\Customer;

use App\Models\BusinessSetting;
use App\Models\CustomerPointLedger;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class PointLedgerService
{
    public function earn(int $customerId, float $amount, array $context): int
    {
        $points = $this->pointsForPurchaseAmount($amount);
        if ($points <= 0) {
            return 0;
        }

        $ledger = $this->write($customerId, $points, array_merge($context, [
            'point_type' => 'earn',
            'amount_value' => $amount,
        ]));

        return $ledger?->points > 0 ? $ledger->points : 0;
    }

    public function redeem(int $customerId, int $points, array $context): float
    {
        if ($points <= 0) {
            return 0;
        }

        $amount = $this->amountForPoints($points);
        $this->write($customerId, -$points, array_merge($context, [
            'point_type' => 'redeem',
            'amount_value' => $amount,
        ]));

        return $amount;
    }

    public function reverseEarn(int $customerId, int $points, array $context): int
    {
        if ($points <= 0) {
            return 0;
        }

        $ledger = $this->write($customerId, -$points, array_merge($context, [
            'point_type' => 'reverse',
            'amount_value' => 0,
        ]));

        return abs((int) ($ledger?->points ?? 0));
    }

    public function amountForPoints(int $points): float
    {
        $rate = max((float) (BusinessSetting::where('key', 'loyalty_point_exchange_rate')->first()?->value ?? 0), 0);
        if ($rate <= 0) {
            return 0;
        }

        return round($points / $rate, 8);
    }

    public function pointsForPurchaseAmount(float $amount): int
    {
        $settings = BusinessSetting::whereIn('key', [
            'loyalty_point_status',
            'loyalty_point_item_purchase_point',
        ])->pluck('value', 'key');

        if ((int) ($settings['loyalty_point_status'] ?? 0) !== 1) {
            return 0;
        }

        return (int) floor($amount * ((float) ($settings['loyalty_point_item_purchase_point'] ?? 0)) / 100);
    }

    public function write(int $customerId, int $signedPoints, array $context): ?CustomerPointLedger
    {
        if ($signedPoints === 0 || ! Schema::hasTable('customer_point_ledgers')) {
            return null;
        }

        return DB::transaction(function () use ($customerId, $signedPoints, $context) {
            $customer = User::query()->lockForUpdate()->findOrFail($customerId);

            $existing = CustomerPointLedger::where('customer_id', $customerId)
                ->where('source_type', $context['source_type'] ?? 'manual')
                ->where('source_id', $context['source_id'] ?? null)
                ->where('point_type', $context['point_type'] ?? 'adjustment')
                ->lockForUpdate()
                ->first();
            if ($existing) {
                return $existing;
            }

            $current = (int) ($customer->loyalty_point ?? 0);
            $next = $current + $signedPoints;
            if ($next < 0) {
                throw ValidationException::withMessages([
                    'points' => translate('Insufficient customer points.'),
                ]);
            }

            $customer->loyalty_point = $next;
            $customer->save();

            $ledger = CustomerPointLedger::create([
                'customer_id' => $customer->id,
                'source_type' => $context['source_type'] ?? 'manual',
                'source_id' => $context['source_id'] ?? null,
                'source_reference' => $context['source_reference'] ?? null,
                'point_type' => $context['point_type'] ?? 'adjustment',
                'points' => $signedPoints,
                'amount_value' => (float) ($context['amount_value'] ?? 0),
                'balance_after' => $next,
                'note' => $context['note'] ?? null,
                'created_by_admin_id' => $context['created_by_admin_id'] ?? null,
            ]);

            $this->mirrorLegacyTransaction($ledger);

            return $ledger;
        });
    }

    private function mirrorLegacyTransaction(CustomerPointLedger $ledger): void
    {
        if (! Schema::hasTable('loyalty_point_transactions')) {
            return;
        }

        DB::table('loyalty_point_transactions')->insert([
            'user_id' => $ledger->customer_id,
            'transaction_id' => (string) Str::uuid(),
            'credit' => $ledger->points > 0 ? $ledger->points : 0,
            'debit' => $ledger->points < 0 ? abs($ledger->points) : 0,
            'balance' => $ledger->balance_after,
            'reference' => $ledger->source_reference ?: $ledger->source_id,
            'transaction_type' => match ($ledger->point_type) {
                'earn' => 'order_place',
                'redeem' => 'point_redeem',
                'reverse' => 'point_reverse',
                default => 'adjustment',
            },
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }
}
