<?php

namespace App\Services;

use App\Models\ActivationHistory;
use App\Models\User;
use App\Support\WelcomeOfferSettings;
use Carbon\CarbonInterface;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Schema;

class WelcomeOfferService
{
    public const TRACKED_LEVELS = [1, 2];

    private const VERIFIED_HISTORY_EVENTS = [
        'voucher_verified',
        'direct_payment_verified',
        'admin_request_approved',
        'admin_manual_verified',
    ];

    public function __construct(private ReferralNetworkService $networkService)
    {
    }

    public function progressForUser(?User $user): array
    {
        if (!$user) {
            return $this->trackedLevelCards($this->emptyLevelMap());
        }

        return $this->trackedLevelCards(
            $this->buildLevelMap($user, $this->maxTrackedLevel())
        );
    }

    /**
     * @param  array<int, Collection<int, mixed>>  $levelMap
     * @return array<int, array<string, mixed>>
     */
    public function trackedLevelCards(array $levelMap): array
    {
        $cards = [];
        $targets = WelcomeOfferSettings::targets();

        foreach (self::TRACKED_LEVELS as $level) {
            $target = (int) ($targets[$level] ?? 0);
            $verifiedMembers = $this->verifiedMembersFromCollection(
                $this->decorateMembers($levelMap[$level] ?? collect())
            );
            $count = $verifiedMembers->count();

            $cards[$level] = [
                'level' => $level,
                'label' => $this->levelLabel($level),
                'target' => $target,
                'count' => $count,
                'remaining' => max(0, $target - $count),
                'progress_percent' => $target > 0 ? min(100, (int) round(($count / $target) * 100)) : 0,
                'is_complete' => $count >= $target,
                'members' => $verifiedMembers,
            ];
        }

        return $cards;
    }

    public function verifiedMembersAtLevel(User $user, int $level, string $search = ''): Collection
    {
        if (!in_array($level, self::TRACKED_LEVELS, true)) {
            return collect();
        }

        $levelMap = $this->buildLevelMap($user, $level);
        $members = $this->verifiedMembersFromCollection(
            $this->decorateMembers($levelMap[$level] ?? collect())
        );
        $search = trim($search);

        if ($search !== '') {
            $members = $members->filter(function ($member) use ($search) {
                return str_contains(mb_strtolower((string) data_get($member, 'name')), mb_strtolower($search))
                    || str_contains(mb_strtolower((string) data_get($member, 'referral_code')), mb_strtolower($search))
                    || str_contains((string) data_get($member, 'mobile'), $search);
            });
        }

        return $members
            ->sortByDesc(function ($member) {
                $verifiedAt = data_get($member, 'welcome_offer_verified_at');

                if ($verifiedAt instanceof CarbonInterface) {
                    return $verifiedAt->timestamp;
                }

                return $this->normalizeDate(data_get($member, 'created_at'))?->timestamp ?? 0;
            })
            ->values();
    }

    public function levelLabel(int $level): string
    {
        $labels = WelcomeOfferSettings::labels();

        return (string) ($labels[$level] ?? ('লেভেল ' . $level));
    }

    public function levelTarget(int $level): int
    {
        $targets = WelcomeOfferSettings::targets();

        return (int) ($targets[$level] ?? 0);
    }

    private function maxTrackedLevel(): int
    {
        return max(self::TRACKED_LEVELS);
    }

    /**
     * @return array<int, Collection<int, mixed>>
     */
    private function buildLevelMap(User $user, int $maxDepth): array
    {
        return $this->networkService->buildLevelMap(
            collect([$user]),
            fn (Collection $parents, array $excludedIds) => $this->downlinesForParents($parents, $excludedIds),
            $maxDepth
        );
    }

    private function downlinesForParents(Collection $parents, array $excludedIds = []): Collection
    {
        $parentCodes = $parents->pluck('referral_code')
            ->map(fn ($value) => trim((string) $value))
            ->filter(fn ($value) => $value !== '')
            ->unique()
            ->values();

        $parentIds = $parents->pluck('id')
            ->map(fn ($value) => trim((string) $value))
            ->filter(fn ($value) => $value !== '')
            ->unique()
            ->values();

        if ($parentCodes->isEmpty() && $parentIds->isEmpty()) {
            return collect();
        }

        return User::query()
            ->where(function ($query) use ($parentCodes, $parentIds) {
                if ($parentCodes->isNotEmpty()) {
                    $query->whereIn('referred_by', $parentCodes->all());
                }

                if ($parentIds->isNotEmpty()) {
                    $query->orWhereIn('referred_by', $parentIds->all());
                }
            })
            ->when($excludedIds !== [], fn ($query) => $query->whereNotIn('id', $excludedIds))
            ->get()
            ->unique('id')
            ->values();
    }

    private function verifiedMembersFromCollection(Collection $members): Collection
    {
        $launchStartsAt = WelcomeOfferSettings::launchStartsAt();

        return $members
            ->filter(fn ($member) => (int) data_get($member, 'is_active', 0) === 1)
            ->filter(function ($member) {
                $welcomeOfferEnabled = data_get($member, 'welcome_offer_referral_enabled');

                return $welcomeOfferEnabled === null || (int) $welcomeOfferEnabled === 1;
            })
            ->filter(function ($member) use ($launchStartsAt) {
                $verifiedAt = $this->effectiveWelcomeOfferVerifiedAt($member);

                return $verifiedAt instanceof CarbonInterface
                    && $verifiedAt->greaterThanOrEqualTo($launchStartsAt);
            })
            ->unique('id')
            ->values();
    }

    private function decorateMembers(Collection $members): Collection
    {
        if ($members->isEmpty()) {
            return collect();
        }

        $historyMap = $this->verifiedHistoryMap(
            $members->pluck('id')
                ->map(fn ($value) => (int) $value)
                ->filter(fn ($value) => $value > 0)
                ->all()
        );

        $uplineMap = $this->uplineMapForMembers($members);

        return $members->map(function ($member) use ($historyMap, $uplineMap) {
            $history = $historyMap[(int) $member->id] ?? null;
            $referredBy = trim((string) ($member->referred_by ?? ''));
            $upline = $uplineMap[$referredBy] ?? null;

            $member->welcome_offer_verified_at = $this->effectiveWelcomeOfferVerifiedAt(
                $member,
                $history['verified_at'] ?? $this->normalizeDate($member->activated_at)
            );
            $member->welcome_offer_verified_via = $history['verified_via'] ?? 'Current verified member';
            $member->welcome_offer_upline_name = $upline?->name ?? '—';
            $member->welcome_offer_upline_referral = $upline?->referral_code ?? '—';
            $member->welcome_offer_joined_at = $this->normalizeDate(data_get($member, 'created_at'));

            return $member;
        })->values();
    }

    /**
     * @param  array<int, int>  $userIds
     * @return array<int, array{verified_at: ?CarbonInterface, verified_via: string}>
     */
    private function verifiedHistoryMap(array $userIds): array
    {
        if ($userIds === [] || !Schema::hasTable('activation_histories')) {
            return [];
        }

        return ActivationHistory::query()
            ->whereIn('user_id', $userIds)
            ->whereIn('event', self::VERIFIED_HISTORY_EVENTS)
            ->orderByDesc('happened_at')
            ->orderByDesc('id')
            ->get(['id', 'user_id', 'event', 'verification_channel', 'source', 'happened_at', 'created_at'])
            ->unique('user_id')
            ->mapWithKeys(function (ActivationHistory $history) {
                $verifiedAt = $history->happened_at ?: $history->created_at;

                return [
                    (int) $history->user_id => [
                        'verified_at' => $verifiedAt,
                        'verified_via' => $this->historyViaLabel($history),
                    ],
                ];
            })
            ->all();
    }

    private function effectiveWelcomeOfferVerifiedAt(mixed $member, ?CarbonInterface $fallback = null): ?CarbonInterface
    {
        $verifiedAt = $fallback;
        if (!$verifiedAt instanceof CarbonInterface) {
            $verifiedAt = data_get($member, 'welcome_offer_verified_at');
        }
        if (!$verifiedAt instanceof CarbonInterface) {
            $verifiedAt = $this->normalizeDate(data_get($member, 'activated_at'));
        }

        $enabledAt = $this->normalizeDate(data_get($member, 'welcome_offer_referral_enabled_at'));
        if ($enabledAt instanceof CarbonInterface && (!$verifiedAt || $enabledAt->greaterThan($verifiedAt))) {
            return $enabledAt;
        }

        return $verifiedAt;
    }

    /**
     * @return array<string, User>
     */
    private function uplineMapForMembers(Collection $members): array
    {
        $keys = $members->pluck('referred_by')
            ->map(fn ($value) => trim((string) $value))
            ->filter(fn ($value) => $value !== '')
            ->unique()
            ->values();

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

        $codes = $keys->filter(fn ($value) => !ctype_digit($value))->values();
        $ids = $keys->filter(fn ($value) => ctype_digit($value))->map(fn ($value) => (int) $value)->values();

        $users = User::query()
            ->where(function ($query) use ($codes, $ids) {
                if ($codes->isNotEmpty()) {
                    $query->whereIn('referral_code', $codes->all());
                }

                if ($ids->isNotEmpty()) {
                    $query->orWhereIn('id', $ids->all());
                }
            })
            ->get(['id', 'name', 'referral_code']);

        $map = [];

        foreach ($users as $user) {
            if ($user->referral_code) {
                $map[(string) $user->referral_code] = $user;
            }

            $map[(string) $user->id] = $user;
        }

        return $map;
    }

    private function historyViaLabel(ActivationHistory $history): string
    {
        return match ((string) $history->event) {
            'voucher_verified' => 'ভাউচার ব্যালেন্স দিয়ে ভেরিফাই',
            'direct_payment_verified' => 'পেমেন্ট callback / API ভেরিফাই',
            'admin_request_approved' => 'অ্যাডমিন রিকোয়েস্ট approve',
            'admin_manual_verified' => 'অ্যাডমিন ম্যানুয়াল verify',
            default => 'ভেরিফাই history',
        };
    }

    private function normalizeDate(mixed $value): ?CarbonInterface
    {
        if ($value instanceof CarbonInterface) {
            return $value;
        }

        if ($value instanceof \DateTimeInterface) {
            return Carbon::instance($value);
        }

        if (is_string($value) && trim($value) !== '') {
            return Carbon::parse($value);
        }

        return null;
    }

    /**
     * @return array<int, Collection<int, mixed>>
     */
    private function emptyLevelMap(): array
    {
        $map = [];

        foreach (self::TRACKED_LEVELS as $level) {
            $map[$level] = collect();
        }

        return $map;
    }
}
