<?php

namespace App\Support;

use App\Models\Lead;
use App\Models\LeadAdjustment;
use App\Models\ReferralEvent;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

final class LeadBalanceLedger
{
    public function adjust(
        Lead $lead,
        float $delta,
        string $action,
        string $token,
        ?User $admin = null,
        string $unit = 'coins',
        ?float $requestedAmount = null,
        ?string $note = null
    ): LeadAdjustment {
        return DB::transaction(function () use ($lead, $delta, $action, $token, $admin, $unit, $requestedAmount, $note) {
            $existing = LeadAdjustment::where('token', $token)->first();
            if ($existing) {
                if ($existing->lead_id !== $lead->id || $existing->action !== $action) {
                    throw ValidationException::withMessages([
                        'adjustment_token' => 'This adjustment token has already been used for another operation.',
                    ]);
                }

                return $existing;
            }

            $locked = Lead::whereKey($lead->id)->lockForUpdate()->firstOrFail();
            $original = max(0, (float) ($locked->original_points ?? $locked->points));
            $before = max(0, (float) $locked->credited_points);
            $after = round($before + $delta, 4);

            if ($delta == 0.0 || $after < 0 || $after > $original) {
                throw ValidationException::withMessages([
                    'adjustment_amount' => 'The adjustment must leave credited coins between 0 and '.number_format($original, 4).'.',
                ]);
            }

            $user = User::whereKey($locked->user_id)->lockForUpdate()->firstOrFail();
            $user->update([
                'current_points' => round((float) $user->current_points + $delta, 4),
                'today_points' => max(0, round((float) $user->today_points + $delta, 4)),
                'total_points' => max(0, round((float) $user->total_points + $delta, 4)),
            ]);

            $locked->forceFill([
                'original_points' => $original,
                'credited_points' => $after,
                'points_reversed_at' => $after <= 0 ? now() : null,
            ])->save();

            $this->syncReferralCommission($locked, $user, $after);

            return LeadAdjustment::create([
                'lead_id' => $locked->id,
                'admin_id' => $admin?->id,
                'token' => $token,
                'action' => $action,
                'points' => $delta,
                'credited_before' => $before,
                'credited_after' => $after,
                'requested_unit' => $unit,
                'requested_amount' => $requestedAmount,
                'note' => $note,
            ]);
        }, 3);
    }

    private function syncReferralCommission(Lead $lead, User $user, float $creditedPoints): void
    {
        if (!$user->referred_by || !site_bool('referral_enabled', true)) {
            return;
        }

        $referrer = User::whereKey($user->referred_by)->lockForUpdate()->first();
        if (!$referrer) {
            return;
        }

        $rate = (float) site_setting('referral_commission_percent', 5) / 100;
        $target = round($creditedPoints * $rate, 2);
        $recorded = (float) ReferralEvent::where('lead_id', $lead->id)
            ->where('type', 'commission')->sum('reward_points');
        $difference = round($target - $recorded, 2);

        if ($difference == 0.0) {
            return;
        }

        $referrer->update([
            'current_points' => round((float) $referrer->current_points + $difference, 4),
            'today_points' => max(0, round((float) $referrer->today_points + $difference, 4)),
            'total_points' => max(0, round((float) $referrer->total_points + $difference, 4)),
            'referral_earnings' => max(0, round((float) $referrer->referral_earnings + $difference, 4)),
        ]);

        ReferralEvent::create([
            'referrer_id' => $referrer->id,
            'referred_user_id' => $user->id,
            'lead_id' => $lead->id,
            'type' => 'commission',
            'source_points' => $difference > 0 ? $difference / max($rate, 0.000001) : 0,
            'reward_points' => $difference,
            'meta' => ['source' => 'lead_balance_adjustment', 'credited_points' => $creditedPoints],
        ]);
    }
}
