<?php

declare(strict_types=1);

use App\Framework\DI\ContainerCompiler;
use App\Framework\DI\DefaultContainer;
use App\Framework\DI\DependencyResolver;
use App\Framework\Reflection\CachedReflectionProvider;

beforeEach(function () {
    $this->tempDir = sys_get_temp_dir() . '/container-compiler-test-' . uniqid();
    mkdir($this->tempDir, 0755, true);

    $this->container = new DefaultContainer();
    $this->reflectionProvider = new CachedReflectionProvider();
    $this->dependencyResolver = new DependencyResolver($this->reflectionProvider, $this->container);
    $this->compiler = new ContainerCompiler($this->reflectionProvider, $this->dependencyResolver);

    $this->compiledPath = $this->tempDir . '/compiled-container.php';
});

afterEach(function () {
    // Clean up test directory
    if (is_dir($this->tempDir)) {
        array_map('unlink', glob($this->tempDir . '/*'));
        rmdir($this->tempDir);
    }
});

// Test classes
class ContainerCompilerTestSimpleService
{
    public function getName(): string
    {
        return 'simple';
    }
}

class ContainerCompilerTestServiceWithDependency
{
    public function __construct(private ContainerCompilerTestSimpleService $service)
    {
    }

    public function getServiceName(): string
    {
        return $this->service->getName();
    }
}

interface ContainerCompilerTestServiceInterface
{
    public function getValue(): string;
}

class ContainerCompilerTestConcreteService implements ContainerCompilerTestServiceInterface
{
    public function getValue(): string
    {
        return 'concrete';
    }
}

test('compiles container with simple binding', function () {
    // Arrange
    $this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);

    // Act
    $this->compiler->compile($this->container, $this->compiledPath);

    // Assert
    expect(file_exists($this->compiledPath))->toBeTrue();

    $content = file_get_contents($this->compiledPath);
    expect($content)->toContain('class CompiledContainer implements Container');
    expect($content)->toContain('createContainerCompilerTestSimpleService()');
});

test('compiles container with dependency injection', function () {
    // Arrange
    $this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
    $this->container->bind(ContainerCompilerTestServiceWithDependency::class, ContainerCompilerTestServiceWithDependency::class);

    // Act
    $this->compiler->compile($this->container, $this->compiledPath);

    // Assert
    expect(file_exists($this->compiledPath))->toBeTrue();

    $content = file_get_contents($this->compiledPath);
    expect($content)->toContain('$this->get(\'ContainerCompilerTestSimpleService\')');
});

test('compiles container with singletons', function () {
    // Arrange
    $this->container->singleton(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);

    // Act
    $this->compiler->compile($this->container, $this->compiledPath);

    // Assert
    expect(file_exists($this->compiledPath))->toBeTrue();

    $content = file_get_contents($this->compiledPath);
    expect($content)->toContain('$this->singletons[\'ContainerCompilerTestSimpleService\'] = true');
});

test('loads compiled container successfully', function () {
    // Arrange
    $this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
    $this->compiler->compile($this->container, $this->compiledPath);

    // Act
    $compiledContainer = ContainerCompiler::load($this->compiledPath);

    // Assert
    expect($compiledContainer)->toBeInstanceOf(\App\Framework\DI\Container::class);
    expect($compiledContainer->has(ContainerCompilerTestSimpleService::class))->toBeTrue();

    $instance = $compiledContainer->get(ContainerCompilerTestSimpleService::class);
    expect($instance)->toBeInstanceOf(ContainerCompilerTestSimpleService::class);
    expect($instance->getName())->toBe('simple');
});

test('compiled container resolves dependencies correctly', function () {
    // Arrange
    $this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
    $this->container->bind(ContainerCompilerTestServiceWithDependency::class, ContainerCompilerTestServiceWithDependency::class);
    $this->compiler->compile($this->container, $this->compiledPath);

    // Act
    $compiledContainer = ContainerCompiler::load($this->compiledPath);
    $instance = $compiledContainer->get(ContainerCompilerTestServiceWithDependency::class);

    // Assert
    expect($instance)->toBeInstanceOf(ContainerCompilerTestServiceWithDependency::class);
    expect($instance->getServiceName())->toBe('simple');
});

test('compiled container handles singletons correctly', function () {
    // Arrange
    $this->container->singleton(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
    $this->compiler->compile($this->container, $this->compiledPath);

    // Act
    $compiledContainer = ContainerCompiler::load($this->compiledPath);
    $instance1 = $compiledContainer->get(ContainerCompilerTestSimpleService::class);
    $instance2 = $compiledContainer->get(ContainerCompilerTestSimpleService::class);

    // Assert
    expect($instance1)->toBe($instance2);
});

test('validates compiled container hash correctly', function () {
    // Arrange
    $this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
    $this->compiler->compile($this->container, $this->compiledPath);

    // Act & Assert - should be valid initially
    expect($this->compiler->isCompiledContainerValid($this->container, $this->compiledPath))->toBeTrue();

    // Add new binding to change container state
    $this->container->bind(ContainerCompilerTestServiceWithDependency::class, ContainerCompilerTestServiceWithDependency::class);

    // Should now be invalid due to hash mismatch
    expect($this->compiler->isCompiledContainerValid($this->container, $this->compiledPath))->toBeFalse();
});

test('returns false for non-existent compiled container', function () {
    // Arrange
    $nonExistentPath = $this->tempDir . '/non-existent.php';

    // Act & Assert
    expect($this->compiler->isCompiledContainerValid($this->container, $nonExistentPath))->toBeFalse();
});

test('creates directory if it does not exist', function () {
    // Arrange
    $nestedPath = $this->tempDir . '/nested/deep/compiled-container.php';
    $this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);

    // Act
    $this->compiler->compile($this->container, $nestedPath);

    // Assert
    expect(file_exists($nestedPath))->toBeTrue();
    expect(is_dir(dirname($nestedPath)))->toBeTrue();
});

test('throws exception when loading non-existent compiled container', function () {
    // Arrange
    $nonExistentPath = $this->tempDir . '/non-existent.php';

    // Act & Assert
    expect(fn () => ContainerCompiler::load($nonExistentPath))
        ->toThrow(RuntimeException::class, 'Compiled container not found');
});

test('compiled container throws exception for runtime binding', function () {
    // Arrange
    $this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
    $this->compiler->compile($this->container, $this->compiledPath);
    $compiledContainer = ContainerCompiler::load($this->compiledPath);

    // Act & Assert
    expect(fn () => $compiledContainer->bind('NewClass', 'AnotherClass'))
        ->toThrow(RuntimeException::class, 'Cannot bind to compiled container');
});

test('compiled container throws exception for runtime singleton registration', function () {
    // Arrange
    $this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
    $this->compiler->compile($this->container, $this->compiledPath);
    $compiledContainer = ContainerCompiler::load($this->compiledPath);

    // Act & Assert
    expect(fn () => $compiledContainer->singleton('NewClass', 'AnotherClass'))
        ->toThrow(RuntimeException::class, 'Cannot add singletons to compiled container');
});

test('compiled container allows runtime instance registration', function () {
    // Arrange
    $this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
    $this->compiler->compile($this->container, $this->compiledPath);
    $compiledContainer = ContainerCompiler::load($this->compiledPath);
    $customInstance = new ContainerCompilerTestSimpleService();

    // Act
    $compiledContainer->instance(ContainerCompilerTestSimpleService::class, $customInstance);
    $retrievedInstance = $compiledContainer->get(ContainerCompilerTestSimpleService::class);

    // Assert
    expect($retrievedInstance)->toBe($customInstance);
});

test('gets default compiled container path', function () {
    // Act
    $path = ContainerCompiler::getCompiledContainerPath();

    // Assert
    expect($path)->toContain('compiled-container.php');
    expect(is_dir(dirname($path)))->toBeTrue();
});

test('gets custom compiled container path', function () {
    // Arrange
    $customCacheDir = $this->tempDir . '/custom-cache';

    // Act
    $path = ContainerCompiler::getCompiledContainerPath($customCacheDir);

    // Assert
    expect($path)->toBe($customCacheDir . '/compiled-container.php');
    expect(is_dir($customCacheDir))->toBeTrue();
});

test('compiled container handles unknown class gracefully', function () {
    // Arrange
    $this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
    $this->compiler->compile($this->container, $this->compiledPath);
    $compiledContainer = ContainerCompiler::load($this->compiledPath);

    // Act & Assert
    expect(fn () => $compiledContainer->get('UnknownClass'))
        ->toThrow(InvalidArgumentException::class, 'Class UnknownClass is not bound in the container');
});

test('generated code contains proper metadata', function () {
    // Arrange
    $this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);

    // Act
    $this->compiler->compile($this->container, $this->compiledPath);

    // Assert
    $content = file_get_contents($this->compiledPath);
    expect($content)->toContain('Generated:');
    expect($content)->toContain('Hash:');
    expect($content)->toContain('WARNING: This file is auto-generated');
    expect($content)->toMatch('/Hash: [a-f0-9]{64}/');
});
