<?php

namespace App\Services;

use Illuminate\Support\Collection;

class ReferralNetworkService
{
    public const MAX_LEVEL = 20;

    /**
     * Build a per-level network map using the supplied child fetcher.
     *
     * @param  Collection<int, mixed>  $rootUsers
     * @param  callable(Collection<int, mixed>, array<int, int>): Collection<int, mixed>  $downlineFetcher
     * @return array<int, Collection<int, mixed>>
     */
    public function buildLevelMap(Collection $rootUsers, callable $downlineFetcher, int $maxDepth = self::MAX_LEVEL): array
    {
        $levels = [];
        $currentUsers = $rootUsers->filter()->values();
        $seenIds = $this->seenIdMap($currentUsers);

        for ($level = 1; $level <= $maxDepth; $level++) {
            $nextUsers = collect($downlineFetcher($currentUsers, array_keys($seenIds)) ?? [])
                ->filter()
                ->unique(fn ($user) => $this->memberKey($user))
                ->reject(function ($user) use ($seenIds) {
                    $id = $this->memberId($user);

                    return $id !== null && isset($seenIds[$id]);
                })
                ->values();

            $levels[$level] = $nextUsers;

            if ($nextUsers->isEmpty()) {
                for ($rest = $level + 1; $rest <= $maxDepth; $rest++) {
                    $levels[$rest] = collect();
                }

                break;
            }

            foreach ($nextUsers as $user) {
                $id = $this->memberId($user);
                if ($id !== null) {
                    $seenIds[$id] = $id;
                }
            }

            $currentUsers = $nextUsers;
        }

        if (count($levels) < $maxDepth) {
            for ($level = count($levels) + 1; $level <= $maxDepth; $level++) {
                $levels[$level] = collect();
            }
        }

        ksort($levels);

        return $levels;
    }

    /**
     * @param  array<int, Collection<int, mixed>>  $levelMap
     * @return array<int, array{total:int, active:int}>
     */
    public function summarizeLevels(array $levelMap): array
    {
        $summary = [];

        foreach ($levelMap as $level => $members) {
            $members = $members instanceof Collection ? $members : collect($members);

            $summary[(int) $level] = [
                'total' => (int) $members->count(),
                'active' => (int) $members->filter(fn ($user) => (int) data_get($user, 'is_active', 0) === 1)->count(),
            ];
        }

        ksort($summary);

        return $summary;
    }

    /**
     * @param  array<int, Collection<int, mixed>>  $levelMap
     */
    public function flattenLevels(array $levelMap): Collection
    {
        $allMembers = collect();

        foreach ($levelMap as $level => $members) {
            $members = $members instanceof Collection ? $members : collect($members);

            $allMembers = $allMembers->merge($members->map(function ($member) use ($level) {
                $member->level = (int) $level;

                return $member;
            }));
        }

        return $allMembers->values();
    }

    /**
     * @param  array<int, Collection<int, mixed>>  $levelMap
     */
    public function containsReferralCode(array $levelMap, string $targetCode): bool
    {
        $targetCode = trim($targetCode);

        if ($targetCode === '') {
            return false;
        }

        foreach ($levelMap as $members) {
            $members = $members instanceof Collection ? $members : collect($members);

            $matched = $members->contains(function ($member) use ($targetCode) {
                return trim((string) data_get($member, 'referral_code')) === $targetCode;
            });

            if ($matched) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param  array<int, Collection<int, mixed>>  $levelMap
     * @return array{levels: array<int, array{level:int, count:int}>, total:int}
     */
    public function teamCountsFromLevelMap(array $levelMap): array
    {
        $levels = [];
        $total = 0;

        foreach ($levelMap as $level => $members) {
            $members = $members instanceof Collection ? $members : collect($members);
            $count = (int) $members->count();

            $levels[] = [
                'level' => (int) $level,
                'count' => $count,
            ];

            $total += $count;
        }

        return [
            'levels' => $levels,
            'total' => $total,
        ];
    }

    /**
     * @param  Collection<int, mixed>  $members
     * @return array<int, int>
     */
    private function seenIdMap(Collection $members): array
    {
        $seenIds = [];

        foreach ($members as $member) {
            $id = $this->memberId($member);

            if ($id !== null) {
                $seenIds[$id] = $id;
            }
        }

        return $seenIds;
    }

    private function memberId($member): ?int
    {
        $id = data_get($member, 'id');

        if ($id === null || $id === '') {
            return null;
        }

        return (int) $id;
    }

    private function memberKey($member): string
    {
        $id = $this->memberId($member);

        if ($id !== null) {
            return 'id:' . $id;
        }

        return 'ref:' . trim((string) data_get($member, 'referral_code'));
    }
}
