<?php

namespace App\Services;

use App\Models\CashbackReward;
use App\Models\CashbackRule;
use App\Models\Transaction;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;

class CashbackService
{
    public function __construct(
        protected CashbackWalletService $cashbackWalletService,
        protected SystemSettingService $systemSettingService
    ) {
    }

    public function settings(): array
    {
        return [
            'enabled' => (bool) $this->systemSettingService->get('features', 'cashback_enabled', false),
            'expiry_enabled' => (bool) $this->systemSettingService->get('features', 'cashback_expiry_enabled', false),
            'expiry_days' => max(
                1,
                min(3650, (int) $this->systemSettingService->get('features', 'cashback_expiry_days', 90))
            ),
        ];
    }

    public function updateSettings(bool $enabled, bool $expiryEnabled = false, int $expiryDays = 90): void
    {
        $this->systemSettingService->putMany([
            [
                'section' => 'features',
                'key' => 'cashback_enabled',
                'value' => $enabled,
                'type' => 'boolean',
            ],
            [
                'section' => 'features',
                'key' => 'cashback_expiry_enabled',
                'value' => $expiryEnabled,
                'type' => 'boolean',
            ],
            [
                'section' => 'features',
                'key' => 'cashback_expiry_days',
                'value' => max(1, min(3650, $expiryDays)),
                'type' => 'integer',
            ],
        ]);
    }

    public function applyCashbackIfEligible(Transaction $transaction): ?CashbackReward
    {
        $settings = $this->settings();
        if (! ($settings['enabled'] ?? false)) {
            return null;
        }

        $transaction->loadMissing(['user', 'category', 'plan.network', 'cashbackReward']);

        if ($transaction->status !== 'success') {
            return null;
        }

        if ($transaction->cashbackReward && $transaction->cashbackReward->status === CashbackReward::STATUS_CREDITED) {
            return $transaction->cashbackReward;
        }

        $rule = $this->resolveMatchingRule($transaction);
        if (! $rule) {
            return null;
        }

        $baseAmount = round((float) ($transaction->amount_charged ?? 0), 2);
        if ($baseAmount <= 0) {
            return null;
        }

        $cashbackAmount = $this->computeCashbackAmount($rule, $baseAmount);
        if ($cashbackAmount <= 0) {
            return null;
        }

        return DB::transaction(function () use ($transaction, $rule, $baseAmount, $cashbackAmount) {
            $lockedTransaction = Transaction::query()
                ->whereKey($transaction->id)
                ->lockForUpdate()
                ->firstOrFail();

            if ($lockedTransaction->status !== 'success') {
                return null;
            }

            $reward = CashbackReward::query()
                ->where('transaction_id', $lockedTransaction->id)
                ->lockForUpdate()
                ->first();

            if ($reward && $reward->status === CashbackReward::STATUS_CREDITED) {
                return $reward;
            }

            if (! $reward) {
                $reward = CashbackReward::create([
                    'cashback_rule_id' => $rule->id,
                    'user_id' => $lockedTransaction->user_id,
                    'transaction_id' => $lockedTransaction->id,
                    'internal_reference' => $lockedTransaction->internal_reference,
                    'status' => CashbackReward::STATUS_CREDITED,
                    'base_amount' => $baseAmount,
                    'cashback_amount' => $cashbackAmount,
                    'credited_at' => now(),
                    'meta_json' => [
                        'category_slug' => data_get($lockedTransaction->meta_json, 'category_slug'),
                        'coupon_code' => $lockedTransaction->coupon_code,
                    ],
                ]);
            }

            $ledgerReference = "cashback:reward:{$lockedTransaction->internal_reference}";
            $ledger = $this->cashbackWalletService->credit(
                user: $lockedTransaction->user()->firstOrFail(),
                amount: $cashbackAmount,
                reference: $ledgerReference,
                narration: 'Cashback reward credited',
                meta: [
                    'transaction_id' => $lockedTransaction->id,
                    'cashback_rule_id' => $rule->id,
                    'internal_reference' => $lockedTransaction->internal_reference,
                    'kind' => 'cashback_reward',
                ],
                source: 'reward'
            );

            $reward->update([
                'cashback_rule_id' => $rule->id,
                'status' => CashbackReward::STATUS_CREDITED,
                'base_amount' => $baseAmount,
                'cashback_amount' => $cashbackAmount,
                'wallet_ledger_reference' => $ledger->reference,
                'credited_at' => $reward->credited_at ?: now(),
                'reversed_at' => null,
                'reversal_reason' => null,
            ]);

            $lockedTransaction->update([
                'cashback_rule_id' => $rule->id,
                'cashback_amount' => $cashbackAmount,
            ]);

            return $reward->fresh();
        }, 3);
    }

    protected function resolveMatchingRule(Transaction $transaction): ?CashbackRule
    {
        $now = Carbon::now();

        $rules = CashbackRule::query()
            ->where('status', CashbackRule::STATUS_ACTIVE)
            ->where(function ($query) use ($now) {
                $query->whereNull('starts_at')->orWhere('starts_at', '<=', $now);
            })
            ->where(function ($query) use ($now) {
                $query->whereNull('ends_at')->orWhere('ends_at', '>=', $now);
            })
            ->orderBy('priority')
            ->orderBy('id')
            ->get();

        foreach ($rules as $rule) {
            if (! $this->ruleMatchesTransaction($rule, $transaction)) {
                continue;
            }

            return $rule;
        }

        return null;
    }

    protected function ruleMatchesTransaction(CashbackRule $rule, Transaction $transaction): bool
    {
        $user = $transaction->user;
        $category = $transaction->category;
        $plan = $transaction->plan;
        $networkId = (int) ($plan?->network_id ?? 0);
        $amountCharged = round((float) ($transaction->amount_charged ?? 0), 2);

        if ($amountCharged <= 0) {
            return false;
        }

        if ($rule->min_amount !== null && $amountCharged < (float) $rule->min_amount) {
            return false;
        }

        if ((float) ($transaction->discount_amount ?? 0) > 0 && ! (bool) $rule->allow_with_coupon) {
            return false;
        }

        $allowedCategoryIds = collect((array) ($rule->applicable_category_ids_json ?? []))
            ->filter(fn ($v) => $v !== null && $v !== '')
            ->map(fn ($v) => (int) $v)
            ->values();
        if ($allowedCategoryIds->isNotEmpty() && ! $allowedCategoryIds->contains((int) ($category?->id ?? 0))) {
            return false;
        }

        $allowedNetworkIds = collect((array) ($rule->applicable_network_ids_json ?? []))
            ->filter(fn ($v) => $v !== null && $v !== '')
            ->map(fn ($v) => (int) $v)
            ->values();
        if ($allowedNetworkIds->isNotEmpty() && (! $networkId || ! $allowedNetworkIds->contains($networkId))) {
            return false;
        }

        $allowedTiers = collect((array) ($rule->applicable_customer_tiers_json ?? []))
            ->filter()
            ->map(fn ($v) => strtolower((string) $v))
            ->values();
        if ($allowedTiers->isNotEmpty() && ! $allowedTiers->contains(strtolower((string) ($user?->customer_tier ?? 'subscriber')))) {
            return false;
        }

        $allowedUserIds = collect((array) ($rule->applicable_user_ids_json ?? []))
            ->filter(fn ($v) => $v !== null && $v !== '')
            ->map(fn ($v) => (int) $v)
            ->values();
        if ($allowedUserIds->isNotEmpty() && ! $allowedUserIds->contains((int) ($user?->id ?? 0))) {
            return false;
        }

        return true;
    }

    protected function computeCashbackAmount(CashbackRule $rule, float $baseAmount): float
    {
        $value = round((float) $rule->value, 2);
        if ($value <= 0) {
            return 0.0;
        }

        $cashback = $rule->type === CashbackRule::TYPE_PERCENT
            ? round(($baseAmount * $value) / 100, 2)
            : $value;

        $maxCashback = $rule->max_cashback !== null ? round((float) $rule->max_cashback, 2) : null;
        if ($maxCashback !== null && $maxCashback > 0) {
            $cashback = min($cashback, $maxCashback);
        }

        return round(max($cashback, 0), 2);
    }
}
