<?php

declare(strict_types=1);

use App\Framework\LiveComponents\Polling\PollService;
use App\Framework\LiveComponents\Attributes\Poll;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Discovery\Results\AttributeRegistry;
use App\Framework\Discovery\ValueObjects\DiscoveredAttribute;
use App\Framework\Discovery\ValueObjects\AttributeTarget;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Core\ValueObjects\MethodName;
use App\Framework\DI\Container;

describe('PollService', function () {
    beforeEach(function () {
        // Create mock container
        $this->container = Mockery::mock(Container::class);

        // Create attribute registry with test poll
        $this->attributeRegistry = new AttributeRegistry();

        $this->testPoll = new DiscoveredAttribute(
            className: ClassName::create('App\\Test\\TestComponent'),
            attributeClass: Poll::class,
            target: AttributeTarget::METHOD,
            methodName: MethodName::create('checkData'),
            arguments: [
                'interval' => 2000,
                'enabled' => true,
                'event' => 'test.checked',
                'stopOnError' => false
            ]
        );

        $this->attributeRegistry->add(Poll::class, $this->testPoll);

        // Create discovery registry
        $this->discoveryRegistry = new DiscoveryRegistry(
            attributes: $this->attributeRegistry
        );

        // Create service
        $this->pollService = new PollService(
            $this->discoveryRegistry,
            $this->container
        );
    });

    afterEach(function () {
        Mockery::close();
    });

    it('finds all polls from discovery registry', function () {
        $polls = $this->pollService->getAllPolls();

        expect($polls)->toHaveCount(1);
        expect($polls[0]['poll'])->toBeInstanceOf(Poll::class);
        expect($polls[0]['discovered'])->toBeInstanceOf(DiscoveredAttribute::class);
    });

    it('reconstructs poll attribute from arguments', function () {
        $polls = $this->pollService->getAllPolls();
        $poll = $polls[0]['poll'];

        expect($poll->interval)->toBe(2000);
        expect($poll->enabled)->toBeTrue();
        expect($poll->event)->toBe('test.checked');
        expect($poll->stopOnError)->toBeFalse();
    });

    it('gets polls for specific class', function () {
        $polls = $this->pollService->getPollsForClass('App\\Test\\TestComponent');

        expect($polls)->toHaveCount(1);
        expect($polls[0]['method'])->toBe('checkData');
        expect($polls[0]['poll']->interval)->toBe(2000);
    });

    it('returns empty array for class without polls', function () {
        $polls = $this->pollService->getPollsForClass('App\\Test\\NonExistent');

        expect($polls)->toBeEmpty();
    });

    it('finds specific poll by class and method', function () {
        $poll = $this->pollService->findPoll(
            'App\\Test\\TestComponent',
            'checkData'
        );

        expect($poll)->toBeInstanceOf(Poll::class);
        expect($poll->interval)->toBe(2000);
    });

    it('returns null for non-existent poll', function () {
        $poll = $this->pollService->findPoll(
            'App\\Test\\TestComponent',
            'nonExistentMethod'
        );

        expect($poll)->toBeNull();
    });

    it('checks if method is pollable', function () {
        expect($this->pollService->isPollable(
            'App\\Test\\TestComponent',
            'checkData'
        ))->toBeTrue();

        expect($this->pollService->isPollable(
            'App\\Test\\TestComponent',
            'nonExistentMethod'
        ))->toBeFalse();
    });

    it('counts total polls', function () {
        expect($this->pollService->getPollCount())->toBe(1);
    });

    it('gets only enabled polls', function () {
        // Add disabled poll
        $disabledPoll = new DiscoveredAttribute(
            className: ClassName::create('App\\Test\\DisabledComponent'),
            attributeClass: Poll::class,
            target: AttributeTarget::METHOD,
            methodName: MethodName::create('disabledMethod'),
            arguments: [
                'interval' => 1000,
                'enabled' => false
            ]
        );

        $this->attributeRegistry->add(Poll::class, $disabledPoll);

        $enabledPolls = $this->pollService->getEnabledPolls();

        expect($enabledPolls)->toHaveCount(1);
        expect($enabledPolls[0]['poll']->enabled)->toBeTrue();
    });

    it('executes poll method via container', function () {
        $mockComponent = new class {
            public function checkData(): array
            {
                return ['status' => 'ok'];
            }
        };

        $this->container->shouldReceive('get')
            ->with('App\\Test\\TestComponent')
            ->andReturn($mockComponent);

        $result = $this->pollService->executePoll(
            'App\\Test\\TestComponent',
            'checkData'
        );

        expect($result)->toBe(['status' => 'ok']);
    });

    it('throws exception for non-existent method', function () {
        $mockComponent = new class {
            // No checkData method
        };

        $this->container->shouldReceive('get')
            ->with('App\\Test\\TestComponent')
            ->andReturn($mockComponent);

        $this->pollService->executePoll(
            'App\\Test\\TestComponent',
            'checkData'
        );
    })->throws(BadMethodCallException::class);

    it('wraps execution errors', function () {
        $mockComponent = new class {
            public function checkData(): array
            {
                throw new RuntimeException('Internal error');
            }
        };

        $this->container->shouldReceive('get')
            ->with('App\\Test\\TestComponent')
            ->andReturn($mockComponent);

        $this->pollService->executePoll(
            'App\\Test\\TestComponent',
            'checkData'
        );
    })->throws(RuntimeException::class, 'Failed to execute poll');
});
