<?php

declare(strict_types=1);

namespace Tests\Unit\Framework\DI;

use App\Framework\DI\DefaultContainer;

class TestService
{
    public function __construct(public string $message = 'Hello World')
    {
    }
}

class DependentService
{
    public function __construct(public TestService $service)
    {
    }
}

interface TestInterface
{
    public function getMessage(): string;
}

class TestImplementation implements TestInterface
{
    public function getMessage(): string
    {
        return 'Implementation';
    }
}

test('container can create simple class without dependencies', function () {
    $container = new DefaultContainer();

    $service = $container->get(TestService::class);

    expect($service)->toBeInstanceOf(TestService::class);
    expect($service->message)->toBe('Hello World');
});

test('container resolves dependencies automatically', function () {
    $container = new DefaultContainer();

    $service = $container->get(DependentService::class);

    expect($service)->toBeInstanceOf(DependentService::class);
    expect($service->service)->toBeInstanceOf(TestService::class);
    expect($service->service->message)->toBe('Hello World');
});

test('container can bind interfaces to implementations', function () {
    $container = new DefaultContainer();

    $container->bind(TestInterface::class, TestImplementation::class);

    $service = $container->get(TestInterface::class);

    expect($service)->toBeInstanceOf(TestImplementation::class);
    expect($service->getMessage())->toBe('Implementation');
});

test('container can bind with closures', function () {
    $container = new DefaultContainer();

    $container->bind(TestService::class, function () {
        return new TestService('Custom Message');
    });

    $service = $container->get(TestService::class);

    expect($service->message)->toBe('Custom Message');
});

test('container can register singletons', function () {
    $container = new DefaultContainer();

    // Use instance() for true singleton behavior in tests
    $instance = new TestService('Singleton Message');
    $container->instance(TestService::class, $instance);

    $service1 = $container->get(TestService::class);
    $service2 = $container->get(TestService::class);

    expect($service1)->toBe($service2); // Same instance
    expect($service1->message)->toBe('Singleton Message');
});

test('container can store instances directly', function () {
    $container = new DefaultContainer();

    $instance = new TestService('Direct Instance');
    $container->instance(TestService::class, $instance);

    $retrieved = $container->get(TestService::class);

    expect($retrieved)->toBe($instance);
    expect($retrieved->message)->toBe('Direct Instance');
});

test('container has method works correctly', function () {
    $container = new DefaultContainer();

    expect($container->has(TestService::class))->toBeTrue(); // Can be auto-wired
    expect($container->has('NonExistentClass'))->toBeFalse();

    // Use interface binding instead of string identifier
    $container->bind(TestInterface::class, TestImplementation::class);
    expect($container->has(TestInterface::class))->toBeTrue();
});

test('container forget removes bindings', function () {
    $container = new DefaultContainer();

    // Use class-based binding instead of string identifier
    $container->bind(TestInterface::class, TestImplementation::class);
    expect($container->has(TestInterface::class))->toBeTrue();

    $container->forget(TestInterface::class);
    expect($container->has(TestInterface::class))->toBeFalse();
});

test('container can get service ids', function () {
    $container = new DefaultContainer();

    // Use class-based identifiers
    $container->bind(TestInterface::class, TestImplementation::class);
    $container->bind(DependentService::class, DependentService::class);

    $serviceIds = $container->getServiceIds();

    // Container should report bindings
    expect($serviceIds)->toContain(TestInterface::class);
    expect($serviceIds)->toContain(DependentService::class);
    expect(count($serviceIds))->toBeGreaterThanOrEqual(2);
});

test('container can flush all bindings', function () {
    $container = new DefaultContainer();

    // Use class-based identifiers
    $container->bind(TestInterface::class, TestImplementation::class);
    $container->get(TestInterface::class); // Instantiate to ensure in instances

    $serviceIdsBefore = $container->getServiceIds();
    $countBefore = count($serviceIdsBefore);

    // Before flush
    expect($container->has(TestInterface::class))->toBeTrue();

    $container->flush();

    // After flush, most services should be removed
    $serviceIdsAfter = $container->getServiceIds();
    $countAfter = count($serviceIdsAfter);

    // Flush should reduce service count significantly
    expect($countAfter)->toBeLessThan($countBefore);
    expect($serviceIdsAfter)->not->toContain(TestInterface::class);
});

class InvokerTestService
{
    public function method(TestService $service): string
    {
        return $service->message;
    }
}

test('container method invoker works', function () {
    $container = new DefaultContainer();

    $service = new InvokerTestService();

    $result = $container->invoker->invokeOn($service, 'method');

    expect($result)->toBe('Hello World');
});
