<?php

namespace App\Services;

use App\Models\BalanceLog;
use App\Models\GmailSale;
use App\Models\GmailSellSetting;
use App\Models\IncomeHistory;
use App\Models\User;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Schema;

class GmailSaleStatusService
{
    public function transition(GmailSale $sale, string $nextStatus): bool
    {
        $previousStatus = (string) $sale->status;

        if ($previousStatus === $nextStatus) {
            return false;
        }

        if ($previousStatus === 'completed' && $nextStatus !== 'completed') {
            $this->reverseSaleRewards($sale);
        }

        $sale->status = $nextStatus;
        $sale->save();

        if ($nextStatus === 'completed' && $previousStatus !== 'completed') {
            $sale->loadMissing('user');
            $this->creditSaleRewards($sale);
        }

        return true;
    }

    private function creditSaleRewards(GmailSale $sale): void
    {
        $amount = $this->saleRate($sale);
        if ($amount <= 0) {
            return;
        }

        if ($sale->rate === null) {
            $sale->rate = $amount;
            $sale->save();
        }

        $user = User::query()->lockForUpdate()->find($sale->user_id);
        if (!$user) {
            return;
        }

        $user->balance = round((float) ($user->balance ?? 0) + $amount, 2);
        $user->save();

        $this->createBalanceLog(
            (int) $user->id,
            $amount,
            $this->directRewardType((int) $sale->id)
        );

        IncomeHistory::create([
            'user_id' => $user->id,
            'source' => 'gmail_sale',
            'amount' => $amount,
            'description' => 'অভিনন্দন! আপনি জিমেইল সেল থেকে ' . $amount . '৳ ইনকাম পেয়েছেন। Submission #' . $sale->id . '.',
        ]);

        $this->creditUplineCommissions($user, $sale);
    }

    private function reverseSaleRewards(GmailSale $sale): void
    {
        $user = User::query()->lockForUpdate()->find($sale->user_id);
        if (!$user) {
            return;
        }

        $directAmount = $this->outstandingDirectReward($sale, $user);
        if ($directAmount > 0) {
            $user->balance = round((float) ($user->balance ?? 0) - $directAmount, 2);
            $user->save();

            $this->createBalanceLog(
                (int) $user->id,
                -1 * $directAmount,
                $this->typedDirectRewardExists($sale, $user)
                    ? $this->directRewardType((int) $sale->id)
                    : 'gmail_sale_reward_legacy_reversal:' . $sale->id
            );

            IncomeHistory::create([
                'user_id' => $user->id,
                'source' => 'gmail_sale_reversal',
                'amount' => -1 * $directAmount,
                'description' => 'রিভার্সাল: Gmail submission #' . $sale->id . ' completed থেকে remove/reject হওয়ায় ' . $directAmount . '৳ সমন্বয় করা হয়েছে।',
            ]);
        }

        foreach ($this->outstandingReferralRewards($sale, $user) as $row) {
            $recipientId = (int) ($row['user_id'] ?? 0);
            $amount = round((float) ($row['amount'] ?? 0), 2);
            $level = (int) ($row['level'] ?? 0);
            $typed = (bool) ($row['typed'] ?? false);

            if ($recipientId <= 0 || $amount <= 0) {
                continue;
            }

            $recipient = User::query()->lockForUpdate()->find($recipientId);
            if (!$recipient) {
                continue;
            }

            $recipient->balance = round((float) ($recipient->balance ?? 0) - $amount, 2);
            $recipient->save();

            $type = $typed
                ? $this->referralRewardType((int) $sale->id, $level)
                : 'gmail_sale_referral_legacy_reversal:' . $sale->id . ':' . $recipientId;

            $this->createBalanceLog($recipientId, -1 * $amount, $type);

            $levelText = $level > 0 ? $level . 'য় লেভেলের ' : '';

            IncomeHistory::create([
                'user_id' => $recipientId,
                'source' => 'gmail_sale_referral_reversal',
                'amount' => -1 * $amount,
                'description' => 'রিভার্সাল: ' . $levelText . $user->name . ' (' . $user->referral_code . ') এর Gmail submission #' . $sale->id . ' কমিশন ' . $amount . '৳ সমন্বয় করা হয়েছে।',
            ]);
        }
    }

    private function creditUplineCommissions(User $user, GmailSale $sale): void
    {
        $commissions = $this->saleUplineCommissions($sale);

        if ($sale->upline_commissions_snapshot === null) {
            $sale->upline_commissions_snapshot = $commissions;
            $sale->save();
        }

        if (!is_array($commissions)) {
            return;
        }

        $level = 1;
        $referrerCode = trim((string) ($user->referred_by ?? ''));

        foreach ($commissions as $amount) {
            if ($referrerCode === '') {
                break;
            }

            $upline = $this->findUplineByReference($referrerCode);
            if (!$upline) {
                break;
            }

            $commission = round((float) $amount, 2);

            if ($commission > 0) {
                $upline->balance = round((float) ($upline->balance ?? 0) + $commission, 2);
                $upline->save();

                $this->createBalanceLog(
                    (int) $upline->id,
                    $commission,
                    $this->referralRewardType((int) $sale->id, $level)
                );

                IncomeHistory::create([
                    'user_id' => $upline->id,
                    'source' => 'gmail_sale_referral',
                    'amount' => $commission,
                    'description' => "অভিনন্দন! আপনি {$level}য় লেভেলের {$user->name} ({$user->referral_code}) এর Gmail submission #{$sale->id} থেকে {$commission}৳ কমিশন পেয়েছেন।",
                ]);
            }

            $referrerCode = trim((string) ($upline->referred_by ?? ''));
            $level++;
        }
    }

    private function outstandingDirectReward(GmailSale $sale, User $user): float
    {
        if ($this->typedDirectRewardExists($sale, $user)) {
            return round((float) BalanceLog::query()
                ->where('user_id', $user->id)
                ->where('type', $this->directRewardType((int) $sale->id))
                ->sum('amount'), 2);
        }

        return $this->inferLegacyDirectReward($sale, $user);
    }

    private function outstandingReferralRewards(GmailSale $sale, User $user): array
    {
        $typed = $this->typedReferralRewards($sale);
        if ($typed !== []) {
            return $typed;
        }

        return $this->inferLegacyReferralRewards($sale, $user);
    }

    private function typedDirectRewardExists(GmailSale $sale, User $user): bool
    {
        if (!$this->supportsBalanceLogType()) {
            return false;
        }

        return BalanceLog::query()
            ->where('user_id', $user->id)
            ->where('type', $this->directRewardType((int) $sale->id))
            ->exists();
    }

    private function typedReferralRewards(GmailSale $sale): array
    {
        if (!$this->supportsBalanceLogType()) {
            return [];
        }

        $prefix = $this->referralRewardPrefix((int) $sale->id) . '%';

        return BalanceLog::query()
            ->selectRaw('user_id, type, SUM(amount) as total_amount')
            ->where('type', 'like', $prefix)
            ->groupBy('user_id', 'type')
            ->get()
            ->map(function ($row) {
                $amount = round((float) ($row->total_amount ?? 0), 2);
                $level = $this->levelFromType((string) ($row->type ?? ''));

                if ($amount <= 0) {
                    return null;
                }

                return [
                    'user_id' => (int) $row->user_id,
                    'amount' => $amount,
                    'level' => $level,
                    'typed' => true,
                ];
            })
            ->filter()
            ->values()
            ->all();
    }

    private function inferLegacyDirectReward(GmailSale $sale, User $user): float
    {
        $query = IncomeHistory::query()
            ->where('user_id', $user->id)
            ->where('source', 'gmail_sale')
            ->where('amount', '>', 0);

        $exact = (clone $query)
            ->where('description', 'like', '%Submission #' . $sale->id . '%')
            ->latest('id')
            ->first();

        if ($exact) {
            return round((float) $exact->amount, 2);
        }

        if ($sale->rate !== null) {
            return round((float) $sale->rate, 2);
        }

        $histories = $this->legacyWindowQuery(clone $query, $sale)->get();

        if ($histories->isEmpty()) {
            return 0.0;
        }

        $completionTime = $this->completionTimestamp($sale);

        $best = $histories
            ->sortBy(fn (IncomeHistory $history) => abs($history->created_at->getTimestamp() - $completionTime))
            ->first();

        return round((float) ($best ? $best->amount : 0), 2);
    }

    private function inferLegacyReferralRewards(GmailSale $sale, User $user): array
    {
        $query = IncomeHistory::query()
            ->where('source', 'gmail_sale_referral')
            ->where('amount', '>', 0);

        $referralCode = trim((string) ($user->referral_code ?? ''));
        if ($referralCode !== '') {
            $query->where('description', 'like', '%(' . $this->escapeLike($referralCode) . ')%');
        }

        $exact = (clone $query)
            ->where('description', 'like', '%Submission #' . $sale->id . '%')
            ->get();

        if ($exact->isNotEmpty()) {
            return $this->mapLegacyReferralRows($exact, $this->completionTimestamp($sale));
        }

        $rows = $this->legacyWindowQuery(clone $query, $sale)->get();

        if ($rows->isEmpty()) {
            return [];
        }

        return $this->mapLegacyReferralRows($rows, $this->completionTimestamp($sale));
    }

    private function mapLegacyReferralRows(Collection $rows, int $completionTime): array
    {
        return $rows
            ->groupBy('user_id')
            ->map(function (Collection $group) use ($completionTime) {
                /** @var \App\Models\IncomeHistory|null $best */
                $best = $group
                    ->sortBy(fn (IncomeHistory $history) => abs($history->created_at->getTimestamp() - $completionTime))
                    ->first();

                if (!$best) {
                    return null;
                }

                return [
                    'user_id' => (int) $best->user_id,
                    'amount' => round((float) $best->amount, 2),
                    'level' => $this->extractLevelFromDescription((string) ($best->description ?? '')),
                    'typed' => false,
                ];
            })
            ->filter(fn ($row) => !empty($row['amount']))
            ->values()
            ->all();
    }

    private function legacyWindowQuery($query, GmailSale $sale)
    {
        $completedAt = $sale->updated_at ?? $sale->created_at ?? now();
        $start = $completedAt->copy()->subMinutes(2);
        $end = $completedAt->copy()->addMinutes(2);

        return $query->whereBetween('created_at', [$start, $end]);
    }

    private function completionTimestamp(GmailSale $sale): int
    {
        return ($sale->updated_at ?? $sale->created_at ?? now())->getTimestamp();
    }

    private function createBalanceLog(int $userId, float $amount, ?string $type = null): void
    {
        $payload = [
            'user_id' => $userId,
            'amount' => $amount,
        ];

        if ($type !== null && $this->supportsBalanceLogType()) {
            $payload['type'] = $type;
        }

        BalanceLog::create($payload);
    }

    private function findUplineByReference(string $reference): ?User
    {
        $reference = trim($reference);
        if ($reference === '') {
            return null;
        }

        $user = User::query()
            ->where('referral_code', $reference)
            ->lockForUpdate()
            ->first();

        if ($user || !ctype_digit($reference)) {
            return $user;
        }

        return User::query()
            ->whereKey((int) $reference)
            ->lockForUpdate()
            ->first();
    }

    private function supportsBalanceLogType(): bool
    {
        return Schema::hasTable('balance_logs') && Schema::hasColumn('balance_logs', 'type');
    }

    private function directRewardType(int $saleId): string
    {
        return 'gmail_sale_reward:' . $saleId;
    }

    private function referralRewardPrefix(int $saleId): string
    {
        return 'gmail_sale_referral:' . $saleId . ':';
    }

    private function referralRewardType(int $saleId, int $level): string
    {
        return $this->referralRewardPrefix($saleId) . $level;
    }

    private function levelFromType(string $type): int
    {
        if (preg_match('/:(\d+)$/', $type, $matches) !== 1) {
            return 0;
        }

        return (int) ($matches[1] ?? 0);
    }

    private function extractLevelFromDescription(string $description): int
    {
        if (preg_match('/(\d+)\s*[য়য়]/u', $description, $matches) !== 1) {
            return 0;
        }

        return (int) ($matches[1] ?? 0);
    }

    private function escapeLike(string $value): string
    {
        return str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $value);
    }

    private function saleRate(GmailSale $sale): float
    {
        if ($sale->rate !== null) {
            return round((float) $sale->rate, 2);
        }

        return round((float) (GmailSellSetting::query()->value('price') ?? 0), 2);
    }

    private function saleUplineCommissions(GmailSale $sale): array
    {
        $snapshot = $sale->upline_commissions_snapshot;
        if (is_array($snapshot) && count($snapshot) > 0) {
            return array_values(array_map('floatval', $snapshot));
        }

        $setting = GmailSellSetting::query()->first();
        $commissions = $setting ? $setting->upline_commissions : null;

        if (!is_array($commissions) || count($commissions) === 0) {
            return [0.50, 0.25, 0.15, 0.05, 0.05];
        }

        return array_values(array_map('floatval', $commissions));
    }
}
