<?php

declare(strict_types=1);

namespace Tests\Feature;

use App\Features\Agent\Models\TelegramProfile;
use App\Features\Auth\Models\User;
use App\Features\Monitoring\Support\InfrastructureMonitorKeys;
use App\Features\Monitoring\Services\InfrastructureHealthService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Redis;
use Tests\TestCase;

class InfrastructureHealthTest extends TestCase
{
    use RefreshDatabase;

    private User $admin;

    /** @var list<string> */
    private array $expectedMonitorKeys = [
        'database',
        'redis',
        'queue',
        'scheduler',
        'telegram',
        'storage',
        'disk_usage',
        'php_version',
    ];

    protected function setUp(): void
    {
        parent::setUp();

        $this->admin = User::create([
            'name' => 'Infra Admin',
            'email' => 'infra-admin@test.com',
            'password' => bcrypt('password'),
        ]);
    }

    public function test_health_status_returns_all_live_monitors(): void
    {
        Http::fake();

        Cache::put(InfrastructureMonitorKeys::SCHEDULER_LAST_RUN, now()->toIso8601String(), 600);

        $this->actingAs($this->admin);

        $response = $this->getJson('/api/v1/health-status');

        $response->assertStatus(200)
            ->assertJsonPath('success', true)
            ->assertJsonCount(8, 'infrastructure');

        $keys = collect($response->json('infrastructure'))->pluck('key')->all();
        $this->assertSame($this->expectedMonitorKeys, $keys);

        foreach ($response->json('infrastructure') as $monitor) {
            $this->assertArrayHasKey('status', $monitor);
            $this->assertArrayHasKey('summary', $monitor);
            $this->assertArrayHasKey('last_checked_at', $monitor);
            $this->assertArrayHasKey('metrics', $monitor);
            $this->assertNotSame('', $monitor['summary']);
            $this->assertNotNull($monitor['last_checked_at']);
        }

        $database = collect($response->json('infrastructure'))->firstWhere('key', 'database');
        $this->assertSame('healthy', $database['status']);
        $this->assertTrue($database['metrics']['connected']);
        $this->assertNotNull($database['metrics']['query_latency_ms']);
    }

    public function test_dashboard_refresh_includes_live_infrastructure(): void
    {
        Http::fake();
        Cache::put(InfrastructureMonitorKeys::SCHEDULER_LAST_RUN, now()->toIso8601String(), 600);

        $this->actingAs($this->admin);

        $response = $this->getJson('/api/v1/dashboard/refresh');

        $response->assertStatus(200)
            ->assertJsonCount(8, 'infrastructure');
    }

    public function test_redis_reports_connection_failure_when_unreachable(): void
    {
        putenv('REDIS_HOST=127.0.0.1');
        $_ENV['REDIS_HOST'] = '127.0.0.1';
        $_SERVER['REDIS_HOST'] = '127.0.0.1';

        Redis::shouldReceive('connection')
            ->once()
            ->andThrow(new \RuntimeException('Connection refused'));

        $monitor = app(InfrastructureHealthService::class)->checkRedis();

        $this->assertSame('error', $monitor['status']);
        $this->assertFalse($monitor['metrics']['connected']);
        $this->assertStringContainsString('Connection refused', (string) $monitor['metrics']['error']);
    }

    public function test_queue_reports_error_when_pending_jobs_have_no_workers(): void
    {
        config(['queue.default' => 'database']);

        DB::table('jobs')->insert([
            'queue' => 'default',
            'payload' => json_encode(['job' => 'test']),
            'attempts' => 0,
            'reserved_at' => null,
            'available_at' => now()->timestamp,
            'created_at' => now()->timestamp,
        ]);

        Cache::forget(InfrastructureMonitorKeys::QUEUE_WORKERS);

        $monitor = app(InfrastructureHealthService::class)->checkQueue();

        $this->assertSame('error', $monitor['status']);
        $this->assertSame(0, $monitor['metrics']['running_workers']);
        $this->assertSame(1, $monitor['metrics']['pending_jobs']);
    }

    public function test_queue_reports_warning_when_failed_jobs_exist(): void
    {
        DB::table('failed_jobs')->insert([
            'uuid' => (string) \Illuminate\Support\Str::uuid(),
            'connection' => 'database',
            'queue' => 'default',
            'payload' => json_encode(['job' => 'test']),
            'exception' => 'Test failure',
            'failed_at' => now(),
        ]);

        $monitor = app(InfrastructureHealthService::class)->checkQueue();

        $this->assertSame('warning', $monitor['status']);
        $this->assertSame(1, $monitor['metrics']['failed_jobs']);
    }

    public function test_telegram_reports_failure_when_bot_verification_fails(): void
    {
        TelegramProfile::create([
            'name' => 'Default',
            'bot_token' => 'bad-token',
            'chat_id' => '12345',
            'is_default' => true,
        ]);

        Http::fake([
            'https://api.telegram.org/*' => Http::response([
                'ok' => false,
                'description' => 'Unauthorized',
            ], 401),
        ]);

        $monitor = app(InfrastructureHealthService::class)->checkTelegram();

        $this->assertSame('error', $monitor['status']);
        $this->assertFalse($monitor['metrics']['bot_verified']);
        $this->assertStringContainsString('Unauthorized', (string) $monitor['metrics']['last_error']);
    }

    public function test_scheduler_reports_error_when_last_run_is_stale(): void
    {
        Cache::put(
            InfrastructureMonitorKeys::SCHEDULER_LAST_RUN,
            now()->subMinutes(10)->toIso8601String(),
            600
        );

        $monitor = app(InfrastructureHealthService::class)->checkScheduler();

        $this->assertSame('error', $monitor['status']);
        $this->assertNotNull($monitor['metrics']['last_successful_execution_at']);
    }

    public function test_scheduler_reports_healthy_when_last_run_is_recent(): void
    {
        Cache::put(InfrastructureMonitorKeys::SCHEDULER_LAST_RUN, now()->subSeconds(30)->toIso8601String(), 600);

        $monitor = app(InfrastructureHealthService::class)->checkScheduler();

        $this->assertSame('healthy', $monitor['status']);
        $this->assertNotNull($monitor['metrics']['next_scheduled_execution_at']);
    }

    public function test_php_version_monitor_returns_current_runtime_version(): void
    {
        $monitor = app(InfrastructureHealthService::class)->checkPhpVersion();

        $this->assertSame(PHP_VERSION, $monitor['metrics']['version']);
        $this->assertSame('healthy', $monitor['status']);
    }

    public function test_storage_monitor_reports_writable_status_and_disk_metrics(): void
    {
        $monitor = app(InfrastructureHealthService::class)->checkStorage();

        $this->assertTrue($monitor['metrics']['writable']);
        $this->assertNotNull($monitor['metrics']['free_bytes']);
        $this->assertNotNull($monitor['metrics']['used_bytes']);
    }
}
