<?php

namespace App\Services\Review;

use App\CentralLogics\RegistrationBonusLogic;
use App\Models\BonusClaimLog;
use App\Models\Review;
use App\Models\RegistrationBonusHistory;
use App\Models\User;
use App\Models\BusinessSetting;
use App\Models\LoyaltyPointTransaction;
use Illuminate\Database\QueryException;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class ReviewRewardService
{
    private static ?bool $hasRewardPopupColumns = null;
    private static ?bool $hasBonusClaimLogTable = null;

    public function grantIfEligible(Review $review, ?string $deviceIdentifier = null): array
    {
        try {
            return DB::transaction(function () use ($review, $deviceIdentifier) {
                /** @var Review|null $lockedReview */
                $lockedReview = Review::query()->lockForUpdate()->find($review->id);

                if (! $lockedReview) {
                    return $this->emptyRewardResponse();
                }

                /** @var User|null $user */
                $user = User::query()->lockForUpdate()->find($lockedReview->user_id);
                if (! $user) {
                    return $this->emptyRewardResponse((int) $lockedReview->id);
                }

                $identity = $this->resolveBonusIdentity($user, $deviceIdentifier);
                $settings = $this->getSettings();
                $points = (int) data_get($settings, 'review_reward_point', 0);

                if (Review::hasColumn('status') && (int) $lockedReview->status !== 1) {
                    return $this->rejectReviewReward($lockedReview, $user, $identity, $points, 'review_not_eligible');
                }

                if ((int) data_get($settings, 'review_reward_status', 0) !== 1) {
                    return $this->rejectReviewReward($lockedReview, $user, $identity, $points, 'bonus_disabled');
                }

                if ($points <= 0) {
                    return $this->rejectReviewReward($lockedReview, $user, $identity, $points, 'invalid_bonus_points');
                }

                $existingTransaction = LoyaltyPointTransaction::where([
                    'user_id' => $user->id,
                    'reference' => (string) $lockedReview->id,
                    'transaction_type' => 'review_reward',
                ])->first();

                if ($existingTransaction) {
                    return $this->rejectReviewReward($lockedReview, $user, $identity, $points, 'already_claimed');
                }

                if ($this->userAlreadyClaimedReviewReward($user, (int) $lockedReview->id)) {
                    return $this->rejectReviewReward($lockedReview, $user, $identity, $points, 'already_claimed');
                }

                $duplicateReason = $this->getReviewDuplicateReason($identity, (int) $user->id, (int) $lockedReview->id);
                if ($duplicateReason) {
                    return $this->rejectReviewReward($lockedReview, $user, $identity, $points, $duplicateReason);
                }

                $currentBalance = (int) ($user->loyalty_point ?? 0);
                $popupMessage = $this->resolvePopupMessage($settings, $points);

                $transaction = new LoyaltyPointTransaction();
                $transaction->user_id = $user->id;
                $transaction->transaction_id = Str::uuid();
                $transaction->reference = (string) $lockedReview->id;
                $transaction->transaction_type = 'review_reward';
                $transaction->balance = $currentBalance + $points;
                $transaction->credit = $points;
                $transaction->debit = 0;
                $transaction->created_at = now();
                $transaction->updated_at = now();

                $user->loyalty_point = $currentBalance + $points;
                $user->save();
                $transaction->save();

                if ($this->hasRewardPopupColumns()) {
                    $lockedReview->reward_points = $points;
                    $lockedReview->reward_popup_message = $popupMessage;
                    $lockedReview->reward_points_granted_at = now();
                    $lockedReview->save();
                }

                $this->logReviewRewardAttempt($lockedReview, $user, $identity, $points, true);

                return [
                    'granted' => true,
                    'points' => $points,
                    'review_id' => (int) $lockedReview->id,
                    'popup_message' => $popupMessage,
                ];
            });
        } catch (QueryException $exception) {
            if ($this->handleMissingRewardPopupColumnException($exception)) {
                return $this->grantIfEligible($review, $deviceIdentifier);
            }

            throw $exception;
        }
    }

    public function getPendingPopupForUser(User $user): ?array
    {
        if (! $this->hasRewardPopupColumns()) {
            return null;
        }

        try {
            $review = Review::query()
                ->where('user_id', $user->id)
                ->whereNotNull('reward_points_granted_at')
                ->whereNull('reward_popup_seen_at')
                ->latest('reward_points_granted_at')
                ->first();
        } catch (QueryException $exception) {
            if ($this->handleMissingRewardPopupColumnException($exception)) {
                return null;
            }

            throw $exception;
        }

        if (! $review || (int) ($review->reward_points ?? 0) <= 0) {
            return null;
        }

        return [
            'review_id' => (int) $review->id,
            'points' => (int) $review->reward_points,
            'message' => $review->reward_popup_message ?: $this->resolvePopupMessage($this->getSettings(), (int) $review->reward_points),
        ];
    }

    public function markPopupSeen(User $user, int $reviewId): void
    {
        if (! $this->hasRewardPopupColumns()) {
            return;
        }

        try {
            Review::query()
                ->where('id', $reviewId)
                ->where('user_id', $user->id)
                ->whereNotNull('reward_points_granted_at')
                ->whereNull('reward_popup_seen_at')
                ->update([
                    'reward_popup_seen_at' => now(),
                ]);
        } catch (QueryException $exception) {
            if ($this->handleMissingRewardPopupColumnException($exception)) {
                return;
            }

            throw $exception;
        }
    }

    public function getSettings(): array
    {
        return array_column(
            BusinessSetting::whereIn('key', [
                'review_reward_status',
                'review_reward_point',
                'review_reward_popup_message',
            ])->get()->toArray(),
            'value',
            'key'
        );
    }

    private function resolvePopupMessage(array $settings, int $points): string
    {
        $message = trim((string) data_get($settings, 'review_reward_popup_message', ''));

        return $message !== ''
            ? $message
            : translate('Thanks for your review!') . ' ' . $points . ' ' . translate('reward points have been added to your account.');
    }

    private function emptyRewardResponse(?int $reviewId = null, ?string $blockReason = null): array
    {
        return [
            'granted' => false,
            'points' => 0,
            'review_id' => $reviewId,
            'popup_message' => null,
            'block_reason' => $blockReason,
        ];
    }

    private function rejectReviewReward(Review $review, User $user, array $identity, int $points, string $reason): array
    {
        $this->logReviewRewardAttempt($review, $user, $identity, $points, false, $reason);

        return $this->emptyRewardResponse((int) $review->id, $reason);
    }

    private function resolveBonusIdentity(User $user, ?string $deviceIdentifier): array
    {
        $registrationHistory = RegistrationBonusHistory::where('user_id', $user->id)->latest('id')->first();

        return [
            'email' => RegistrationBonusLogic::normalizeEmailForBonus($user->email ?: $registrationHistory?->email),
            'phone' => RegistrationBonusLogic::normalizePhoneForBonus($user->phone ?: $registrationHistory?->phone),
            'device_identifier' => RegistrationBonusLogic::normalizeDeviceIdentifierForBonus($deviceIdentifier)
                ?: RegistrationBonusLogic::normalizeDeviceIdentifierForBonus($registrationHistory?->device_identifier),
        ];
    }

    private function userAlreadyClaimedReviewReward(User $user, int $reviewId): bool
    {
        $hasTransaction = LoyaltyPointTransaction::where('user_id', $user->id)
            ->where('transaction_type', 'review_reward')
            ->where('reference', '!=', (string) $reviewId)
            ->exists();

        if ($hasTransaction) {
            return true;
        }

        if (! $this->hasBonusClaimLogTable()) {
            return false;
        }

        return BonusClaimLog::where('bonus_type', 'review')
            ->where('granted_status', 1)
            ->where('user_id', $user->id)
            ->where(function ($query) use ($reviewId) {
                $query->whereNull('review_id')->orWhere('review_id', '!=', $reviewId);
            })
            ->exists();
    }

    private function getReviewDuplicateReason(array $identity, int $userId, int $reviewId): ?string
    {
        if (! $this->hasBonusClaimLogTable()) {
            return null;
        }

        if ($this->hasGrantedReviewBonusForIdentity('email', $identity['email'] ?? null, $userId, $reviewId)) {
            return 'duplicate_email';
        }

        if ($this->hasGrantedReviewBonusForIdentity('phone', $identity['phone'] ?? null, $userId, $reviewId)) {
            return 'duplicate_phone';
        }

        if ($this->hasGrantedReviewBonusForIdentity('device_identifier', $identity['device_identifier'] ?? null, $userId, $reviewId)) {
            return 'duplicate_device';
        }

        return null;
    }

    private function hasGrantedReviewBonusForIdentity(string $column, ?string $value, int $userId, int $reviewId): bool
    {
        if (! $value) {
            return false;
        }

        return BonusClaimLog::where('bonus_type', 'review')
            ->where('granted_status', 1)
            ->where($column, $value)
            ->where('user_id', '!=', $userId)
            ->where(function ($query) use ($reviewId) {
                $query->whereNull('review_id')->orWhere('review_id', '!=', $reviewId);
            })
            ->exists();
    }

    private function logReviewRewardAttempt(Review $review, User $user, array $identity, int $points, bool $granted, ?string $reason = null): void
    {
        BonusClaimLog::safeCreate([
            'bonus_type' => 'review',
            'user_id' => $user->id,
            'review_id' => $review->id,
            'email' => $identity['email'] ?? null,
            'phone' => $identity['phone'] ?? null,
            'device_identifier' => $identity['device_identifier'] ?? null,
            'bonus_amount' => 0,
            'bonus_points' => max($points, 0),
            'granted_status' => $granted ? 1 : 0,
            'block_reason' => $granted ? null : $reason,
            'attempted_at' => now(),
            'granted_at' => $granted ? now() : null,
            'context' => [
                'order_id' => $review->order_id,
                'rating' => $review->rating,
            ],
        ]);
    }

    private function hasBonusClaimLogTable(): bool
    {
        if (self::$hasBonusClaimLogTable !== null) {
            return self::$hasBonusClaimLogTable;
        }

        return self::$hasBonusClaimLogTable = Schema::hasTable((new BonusClaimLog())->getTable());
    }

    private function hasRewardPopupColumns(): bool
    {
        if (self::$hasRewardPopupColumns !== null) {
            return self::$hasRewardPopupColumns;
        }

        $table = (new Review())->getTable();

        self::$hasRewardPopupColumns = collect($this->rewardPopupColumns())
            ->every(fn (string $column) => Schema::hasColumn($table, $column));

        return self::$hasRewardPopupColumns;
    }

    private function rewardPopupColumns(): array
    {
        return [
            'reward_points',
            'reward_popup_message',
            'reward_points_granted_at',
            'reward_popup_seen_at',
        ];
    }

    private function handleMissingRewardPopupColumnException(QueryException $exception): bool
    {
        $message = $exception->getMessage();

        foreach ($this->rewardPopupColumns() as $column) {
            if (str_contains($message, $column)) {
                self::$hasRewardPopupColumns = false;
                report($exception);

                return true;
            }
        }

        return false;
    }
}
