<?php

namespace Tests\Unit;

use App\Services\ReferralNetworkService;
use Illuminate\Support\Collection;
use PHPUnit\Framework\TestCase;

class ReferralNetworkServiceTest extends TestCase
{
    public function test_it_counts_a_twenty_level_chain_without_recounting_seen_members(): void
    {
        $service = new ReferralNetworkService();
        $root = $this->member(1, 'ROOT', null, 1);
        $graph = [];
        $nodes = [];
        $previous = $root;

        for ($level = 1; $level <= ReferralNetworkService::MAX_LEVEL; $level++) {
            $node = $this->member($level + 1, 'L' . $level, $previous->referral_code, $level % 2 === 0 ? 1 : 0);
            $graph[$previous->referral_code][] = $node;
            $nodes[$level] = $node;
            $previous = $node;
        }

        $graph[$previous->referral_code][] = $root;
        $graph[$previous->referral_code][] = $nodes[10];

        $levelMap = $service->buildLevelMap(
            new Collection([$root]),
            $this->graphFetcher($graph),
            ReferralNetworkService::MAX_LEVEL
        );

        $summary = $service->summarizeLevels($levelMap);
        $team = $service->teamCountsFromLevelMap($levelMap);

        $this->assertCount(ReferralNetworkService::MAX_LEVEL, $levelMap);
        $this->assertCount(1, $levelMap[ReferralNetworkService::MAX_LEVEL]);
        $this->assertSame('L20', $levelMap[ReferralNetworkService::MAX_LEVEL]->first()->referral_code);
        $this->assertSame(ReferralNetworkService::MAX_LEVEL, array_sum(array_column($summary, 'total')));
        $this->assertSame(10, array_sum(array_column($summary, 'active')));
        $this->assertSame(ReferralNetworkService::MAX_LEVEL, $team['total']);
        $this->assertTrue($service->containsReferralCode($levelMap, 'L20'));
        $this->assertFalse($service->containsReferralCode($levelMap, 'ROOT'));
    }

    public function test_it_fills_remaining_levels_with_zero_counts_after_the_chain_ends(): void
    {
        $service = new ReferralNetworkService();
        $root = $this->member(1, 'ROOT', null, 1);
        $level1 = $this->member(2, 'A1', 'ROOT', 1);
        $level2 = $this->member(3, 'A2', 'A1', 0);
        $level3 = $this->member(4, 'A3', 'A2', 1);

        $graph = [
            'ROOT' => [$level1],
            'A1' => [$level2],
            'A2' => [$level3],
        ];

        $levelMap = $service->buildLevelMap(
            new Collection([$root]),
            $this->graphFetcher($graph),
            ReferralNetworkService::MAX_LEVEL
        );

        $team = $service->teamCountsFromLevelMap($levelMap);

        $this->assertCount(1, $levelMap[1]);
        $this->assertCount(1, $levelMap[2]);
        $this->assertCount(1, $levelMap[3]);
        $this->assertCount(0, $levelMap[4]);
        $this->assertCount(0, $levelMap[ReferralNetworkService::MAX_LEVEL]);
        $this->assertSame(3, $team['total']);
        $this->assertCount(ReferralNetworkService::MAX_LEVEL, $team['levels']);
    }

    private function member(int $id, string $referralCode, ?string $referredBy, int $isActive): object
    {
        return (object) [
            'id' => $id,
            'referral_code' => $referralCode,
            'referred_by' => $referredBy,
            'is_active' => $isActive,
        ];
    }

    private function graphFetcher(array $graph): callable
    {
        return function (Collection $parents, array $excludedIds) use ($graph): Collection {
            $children = [];

            foreach ($parents as $parent) {
                $children = array_merge(
                    $children,
                    $graph[trim((string) $parent->referral_code)] ?? []
                );
            }

            return new Collection($children);
        };
    }
}
