<?php

declare(strict_types=1);

use App\Framework\Config\Environment;
use App\Framework\Core\ValueObjects\Email;
use App\Framework\Deployment\Ssl\Jobs\SslCertificateRenewalJob;
use App\Framework\Deployment\Ssl\Services\SslCertificateService;
use App\Framework\Deployment\Ssl\ValueObjects\CertificateMode;
use App\Framework\Deployment\Ssl\ValueObjects\CertificateStatus;
use App\Framework\Deployment\Ssl\ValueObjects\DomainName;
use App\Framework\Deployment\Ssl\ValueObjects\SslConfiguration;
use App\Framework\Filesystem\FilePath;
use App\Framework\Logging\Logger;
use App\Framework\Worker\Every;
use App\Framework\Worker\Schedule;

describe('SslCertificateRenewalJob', function () {
    beforeEach(function () {
        $this->sslService = Mockery::mock(SslCertificateService::class);
        $this->environment = Mockery::mock(Environment::class);
        $this->logger = Mockery::mock(Logger::class);

        // Setup environment mock
        $this->environment->shouldReceive('get')
            ->with('DOMAIN_NAME', 'michaelschiemer.de')
            ->andReturn('example.com');
        $this->environment->shouldReceive('get')
            ->with('SSL_EMAIL', 'mail@michaelschiemer.de')
            ->andReturn('admin@example.com');
        $this->environment->shouldReceive('get')
            ->with('LETSENCRYPT_STAGING', '0')
            ->andReturn('0');

        $this->job = new SslCertificateRenewalJob(
            $this->sslService,
            $this->environment,
            $this->logger
        );
    });

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

    it('has schedule attribute', function () {
        $reflection = new ReflectionClass(SslCertificateRenewalJob::class);
        $attributes = $reflection->getAttributes(Schedule::class);

        expect($attributes)->toHaveCount(1);

        $schedule = $attributes[0]->newInstance();
        expect($schedule->at)->toBeInstanceOf(Every::class);
        expect($schedule->at->days)->toBe(1);
    });

    it('skips renewal when certificate does not exist', function () {
        $status = CertificateStatus::notFound();

        $this->sslService->shouldReceive('getStatus')
            ->once()
            ->andReturn($status);

        // Renewal should not be attempted
        $this->sslService->shouldNotReceive('renew');

        $this->logger->shouldReceive('info')->once();
        $this->logger->shouldReceive('warning')->once();

        $result = $this->job->handle();

        expect($result['success'])->toBeFalse();
        expect($result['reason'])->toBe('certificate_not_found');
    });

    it('skips renewal when certificate is valid and not expiring', function () {
        $status = new CertificateStatus(
            exists: true,
            isValid: true,
            notBefore: new DateTimeImmutable('-30 days'),
            notAfter: new DateTimeImmutable('+60 days'),
            issuer: 'Let\'s Encrypt',
            subject: 'example.com',
            daysUntilExpiry: 60,
            isExpiring: false,
            isExpired: false
        );

        $this->sslService->shouldReceive('getStatus')
            ->once()
            ->andReturn($status);

        // Renewal should not be attempted
        $this->sslService->shouldNotReceive('renew');

        $this->logger->shouldReceive('info')->twice();

        $result = $this->job->handle();

        expect($result['success'])->toBeTrue();
        expect($result['renewed'])->toBeFalse();
        expect($result['reason'])->toBe('not_needed');
        expect($result['days_until_expiry'])->toBe(60);
    });

    it('renews certificate when expiring', function () {
        $oldStatus = new CertificateStatus(
            exists: true,
            isValid: true,
            notBefore: new DateTimeImmutable('-60 days'),
            notAfter: new DateTimeImmutable('+20 days'),
            issuer: 'Let\'s Encrypt',
            subject: 'example.com',
            daysUntilExpiry: 20,
            isExpiring: true,
            isExpired: false
        );

        $newStatus = new CertificateStatus(
            exists: true,
            isValid: true,
            notBefore: new DateTimeImmutable('now'),
            notAfter: new DateTimeImmutable('+90 days'),
            issuer: 'Let\'s Encrypt',
            subject: 'example.com',
            daysUntilExpiry: 90,
            isExpiring: false,
            isExpired: false
        );

        $this->sslService->shouldReceive('getStatus')
            ->once()
            ->andReturn($oldStatus);

        $this->sslService->shouldReceive('renew')
            ->once()
            ->andReturn($newStatus);

        $this->logger->shouldReceive('info')->times(3);

        $result = $this->job->handle();

        expect($result['success'])->toBeTrue();
        expect($result['renewed'])->toBeTrue();
        expect($result['days_until_expiry'])->toBe(90);
    });

    it('renews certificate when expired', function () {
        $oldStatus = new CertificateStatus(
            exists: true,
            isValid: false,
            notBefore: new DateTimeImmutable('-180 days'),
            notAfter: new DateTimeImmutable('-10 days'),
            issuer: 'Let\'s Encrypt',
            subject: 'example.com',
            daysUntilExpiry: -10,
            isExpiring: false,
            isExpired: true
        );

        $newStatus = new CertificateStatus(
            exists: true,
            isValid: true,
            notBefore: new DateTimeImmutable('now'),
            notAfter: new DateTimeImmutable('+90 days'),
            issuer: 'Let\'s Encrypt',
            subject: 'example.com',
            daysUntilExpiry: 90,
            isExpiring: false,
            isExpired: false
        );

        $this->sslService->shouldReceive('getStatus')
            ->once()
            ->andReturn($oldStatus);

        $this->sslService->shouldReceive('renew')
            ->once()
            ->andReturn($newStatus);

        $this->logger->shouldReceive('info')->times(3);

        $result = $this->job->handle();

        expect($result['success'])->toBeTrue();
        expect($result['renewed'])->toBeTrue();
    });

    it('handles renewal exception gracefully', function () {
        $status = new CertificateStatus(
            exists: true,
            isValid: true,
            notBefore: null,
            notAfter: null,
            issuer: null,
            subject: null,
            daysUntilExpiry: 20,
            isExpiring: true,
            isExpired: false
        );

        $this->sslService->shouldReceive('getStatus')
            ->once()
            ->andReturn($status);

        $this->sslService->shouldReceive('renew')
            ->once()
            ->andThrow(new \RuntimeException('Renewal failed'));

        $this->logger->shouldReceive('info')->twice();
        $this->logger->shouldReceive('error')->once();

        $result = $this->job->handle();

        expect($result['success'])->toBeFalse();
        expect($result['error'])->toBe('Renewal failed');
    });

    it('logs renewal details', function () {
        $oldStatus = new CertificateStatus(
            exists: true,
            isValid: true,
            notBefore: new DateTimeImmutable('2024-01-01'),
            notAfter: new DateTimeImmutable('2024-03-15'),
            issuer: 'Let\'s Encrypt',
            subject: 'example.com',
            daysUntilExpiry: 20,
            isExpiring: true,
            isExpired: false
        );

        $newStatus = new CertificateStatus(
            exists: true,
            isValid: true,
            notBefore: new DateTimeImmutable('2024-03-01'),
            notAfter: new DateTimeImmutable('2024-05-30'),
            issuer: 'Let\'s Encrypt',
            subject: 'example.com',
            daysUntilExpiry: 90,
            isExpiring: false,
            isExpired: false
        );

        $this->sslService->shouldReceive('getStatus')
            ->once()
            ->andReturn($oldStatus);

        $this->sslService->shouldReceive('renew')
            ->once()
            ->andReturn($newStatus);

        $this->logger->shouldReceive('info')
            ->with('Running scheduled SSL certificate renewal check', Mockery::any())
            ->once();

        $this->logger->shouldReceive('info')
            ->with('SSL certificate renewal needed, starting renewal', Mockery::any())
            ->once();

        $this->logger->shouldReceive('info')
            ->with('SSL certificate renewed successfully', Mockery::on(function ($context) {
                return isset($context['old_expiry'])
                    && isset($context['new_expiry'])
                    && isset($context['new_days_until_expiry']);
            }))
            ->once();

        $result = $this->job->handle();

        expect($result['success'])->toBeTrue();
        expect($result['renewed'])->toBeTrue();
    });
});
