<?php

use App\Livewire\Settings\ScheduledJobs;
use App\Models\DockerCleanupExecution;
use App\Models\Environment;
use App\Models\Project;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledDatabaseBackupExecution;
use App\Models\Server;
use App\Models\Service;
use App\Models\ServiceDatabase;
use App\Models\StandaloneDocker;
use App\Models\StandalonePostgresql;
use App\Models\Team;
use App\Models\User;
use App\Services\SchedulerLogParser;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;

uses(RefreshDatabase::class);

function withIsolatedScheduledLogsForMonitoringTest(callable $callback): mixed
{
    $logDir = storage_path('logs');
    if (! is_dir($logDir)) {
        mkdir($logDir, 0755, true);
    }

    $renamed = [];
    foreach (glob($logDir.'/scheduled-*.log') as $log) {
        $tmp = $log.'.scheduled-jobs-test-bak';
        rename($log, $tmp);
        $renamed[$tmp] = $log;
    }

    try {
        return $callback($logDir.'/scheduled-'.now()->format('Y-m-d').'.log');
    } finally {
        foreach (glob($logDir.'/scheduled-*.log') as $log) {
            @unlink($log);
        }

        foreach ($renamed as $tmp => $original) {
            if (file_exists($tmp)) {
                rename($tmp, $original);
            }
        }
    }
}

beforeEach(function () {
    // Create root team (id 0) and root user
    $this->rootTeam = Team::factory()->create(['id' => 0, 'name' => 'Root Team']);
    $this->rootUser = User::factory()->create();
    $this->rootUser->teams()->attach($this->rootTeam, ['role' => 'owner']);

    // Create regular team and user
    $this->regularTeam = Team::factory()->create();
    $this->regularUser = User::factory()->create();
    $this->regularUser->teams()->attach($this->regularTeam, ['role' => 'owner']);
});

test('scheduled jobs page requires instance admin access', function () {
    $this->actingAs($this->regularUser);
    session(['currentTeam' => $this->regularTeam]);

    $response = $this->get(route('settings.scheduled-jobs'));
    $response->assertRedirect(route('dashboard'));
});

test('scheduled jobs page is accessible by instance admin', function () {
    $this->actingAs($this->rootUser);
    session(['currentTeam' => $this->rootTeam]);

    Livewire::test(ScheduledJobs::class)
        ->assertStatus(200)
        ->assertSee('Scheduled Job Issues');
});

test('scheduled jobs page shows failed backup executions', function () {
    $this->actingAs($this->rootUser);
    session(['currentTeam' => $this->rootTeam]);

    $server = Server::factory()->create(['team_id' => $this->rootTeam->id]);

    $backup = ScheduledDatabaseBackup::create([
        'team_id' => $this->rootTeam->id,
        'frequency' => '0 * * * *',
        'database_id' => 1,
        'database_type' => 'App\Models\StandalonePostgresql',
        'enabled' => true,
    ]);

    ScheduledDatabaseBackupExecution::create([
        'scheduled_database_backup_id' => $backup->id,
        'status' => 'failed',
        'message' => 'Backup failed: connection timeout',
    ]);

    Livewire::test(ScheduledJobs::class)
        ->assertStatus(200)
        ->assertSee('Backup');
});

test('scheduled jobs page shows failed cleanup executions', function () {
    $this->actingAs($this->rootUser);
    session(['currentTeam' => $this->rootTeam]);

    $server = Server::factory()->create([
        'team_id' => $this->rootTeam->id,
    ]);

    DockerCleanupExecution::create([
        'server_id' => $server->id,
        'status' => 'failed',
        'message' => 'Cleanup failed: disk full',
    ]);

    Livewire::test(ScheduledJobs::class)
        ->assertStatus(200)
        ->assertSee('Cleanup');
});

test('filter by type works', function () {
    $this->actingAs($this->rootUser);
    session(['currentTeam' => $this->rootTeam]);

    Livewire::test(ScheduledJobs::class)
        ->set('filterType', 'backup')
        ->assertStatus(200)
        ->set('filterType', 'cleanup')
        ->assertStatus(200)
        ->set('filterType', 'task')
        ->assertStatus(200);
});

test('only failed executions are shown', function () {
    $this->actingAs($this->rootUser);
    session(['currentTeam' => $this->rootTeam]);

    $backup = ScheduledDatabaseBackup::create([
        'team_id' => $this->rootTeam->id,
        'frequency' => '0 * * * *',
        'database_id' => 1,
        'database_type' => 'App\Models\StandalonePostgresql',
        'enabled' => true,
    ]);

    ScheduledDatabaseBackupExecution::create([
        'scheduled_database_backup_id' => $backup->id,
        'status' => 'success',
        'message' => 'Backup completed successfully',
    ]);

    ScheduledDatabaseBackupExecution::create([
        'scheduled_database_backup_id' => $backup->id,
        'status' => 'failed',
        'message' => 'Backup failed: connection refused',
    ]);

    Livewire::test(ScheduledJobs::class)
        ->assertSee('Backup failed: connection refused')
        ->assertDontSee('Backup completed successfully');
});

test('filter by date range works', function () {
    $this->actingAs($this->rootUser);
    session(['currentTeam' => $this->rootTeam]);

    Livewire::test(ScheduledJobs::class)
        ->set('filterDate', 'last_7d')
        ->assertStatus(200)
        ->set('filterDate', 'last_30d')
        ->assertStatus(200)
        ->set('filterDate', 'all')
        ->assertStatus(200);
});

test('scheduler log parser returns empty collection when no logs exist', function () {
    $parser = new SchedulerLogParser;

    $skips = $parser->getRecentSkips();
    expect($skips)->toBeEmpty();

    $runs = $parser->getRecentRuns();
    expect($runs)->toBeEmpty();
})->skip(fn () => file_exists(storage_path('logs/scheduled-'.now()->format('Y-m-d').'.log')), 'Skipped: log file already exists from other tests');

test('scheduler log parser parses skip entries correctly', function () {
    $logPath = storage_path('logs/scheduled-'.now()->format('Y-m-d').'.log');
    $logDir = dirname($logPath);
    if (! is_dir($logDir)) {
        mkdir($logDir, 0755, true);
    }

    $logLine = '['.now()->format('Y-m-d H:i:s').'] production.INFO: Backup skipped {"type":"backup","skip_reason":"server_not_functional","execution_time":"'.now()->toIso8601String().'","backup_id":1,"team_id":5}';
    file_put_contents($logPath, $logLine."\n");

    $parser = new SchedulerLogParser;
    $skips = $parser->getRecentSkips();

    expect($skips)->toHaveCount(1);
    expect($skips->first()['type'])->toBe('backup');
    expect($skips->first()['reason'])->toBe('server_not_functional');
    expect($skips->first()['team_id'])->toBe(5);

    // Cleanup
    @unlink($logPath);
});

test('scheduler log parser excludes started events from runs', function () {
    $logPath = storage_path('logs/scheduled-test-started-filter.log');
    $logDir = dirname($logPath);
    if (! is_dir($logDir)) {
        mkdir($logDir, 0755, true);
    }

    // Temporarily rename existing logs so they don't interfere
    $existingLogs = glob(storage_path('logs/scheduled-*.log'));
    $renamed = [];
    foreach ($existingLogs as $log) {
        $tmp = $log.'.bak';
        rename($log, $tmp);
        $renamed[$tmp] = $log;
    }

    $logPath = storage_path('logs/scheduled-'.now()->format('Y-m-d').'.log');
    $lines = [
        '['.now()->format('Y-m-d H:i:s').'] production.INFO: ScheduledJobManager started {}',
        '['.now()->format('Y-m-d H:i:s').'] production.INFO: ScheduledJobManager completed {"duration_ms":74,"dispatched":1,"skipped":13}',
    ];
    file_put_contents($logPath, implode("\n", $lines)."\n");

    $parser = new SchedulerLogParser;
    $runs = $parser->getRecentRuns();

    expect($runs)->toHaveCount(1);
    expect($runs->first()['message'])->toContain('completed');

    // Cleanup
    @unlink($logPath);
    foreach ($renamed as $tmp => $original) {
        rename($tmp, $original);
    }
});

test('scheduler log parser filters by team id', function () {
    $logPath = storage_path('logs/scheduled-'.now()->format('Y-m-d').'.log');
    $logDir = dirname($logPath);
    if (! is_dir($logDir)) {
        mkdir($logDir, 0755, true);
    }

    $lines = [
        '['.now()->format('Y-m-d H:i:s').'] production.INFO: Backup skipped {"type":"backup","skip_reason":"server_not_functional","team_id":1}',
        '['.now()->format('Y-m-d H:i:s').'] production.INFO: Backup skipped {"type":"backup","skip_reason":"subscription_unpaid","team_id":2}',
    ];
    file_put_contents($logPath, implode("\n", $lines)."\n");

    $parser = new SchedulerLogParser;

    $allSkips = $parser->getRecentSkips(100);
    expect($allSkips)->toHaveCount(2);

    $team1Skips = $parser->getRecentSkips(100, 1);
    expect($team1Skips)->toHaveCount(1);
    expect($team1Skips->first()['team_id'])->toBe(1);

    // Cleanup
    @unlink($logPath);
});

test('skipped jobs show fallback when resource is deleted', function () {
    $this->actingAs($this->rootUser);
    session(['currentTeam' => $this->rootTeam]);

    $logPath = storage_path('logs/scheduled-'.now()->format('Y-m-d').'.log');
    $logDir = dirname($logPath);
    if (! is_dir($logDir)) {
        mkdir($logDir, 0755, true);
    }

    // Temporarily rename existing logs so they don't interfere
    $existingLogs = glob(storage_path('logs/scheduled-*.log'));
    $renamed = [];
    foreach ($existingLogs as $log) {
        $tmp = $log.'.bak';
        rename($log, $tmp);
        $renamed[$tmp] = $log;
    }

    $lines = [
        '['.now()->format('Y-m-d H:i:s').'] production.INFO: Task skipped {"type":"task","skip_reason":"application_not_running","task_id":99999,"task_name":"my-cron-job","team_id":0}',
    ];
    file_put_contents($logPath, implode("\n", $lines)."\n");

    Livewire::test(ScheduledJobs::class)
        ->assertStatus(200)
        ->assertSee('my-cron-job')
        ->assertSee('Application not running');

    // Cleanup
    @unlink($logPath);
    foreach ($renamed as $tmp => $original) {
        rename($tmp, $original);
    }
});

test('skipped service database backups render with service backup link', function () {
    $this->actingAs($this->rootUser);
    session(['currentTeam' => $this->rootTeam]);

    $server = Server::factory()->create(['team_id' => $this->rootTeam->id]);
    $destination = StandaloneDocker::where('server_id', $server->id)->firstOrFail();
    $project = Project::factory()->create(['team_id' => $this->rootTeam->id]);
    $environment = Environment::factory()->create(['project_id' => $project->id]);
    $service = Service::factory()->create([
        'server_id' => $server->id,
        'destination_id' => $destination->id,
        'destination_type' => $destination->getMorphClass(),
        'environment_id' => $environment->id,
    ]);
    $serviceDatabase = ServiceDatabase::create([
        'service_id' => $service->id,
        'name' => 'service-postgres',
        'image' => 'postgres:16-alpine',
        'custom_type' => 'postgresql',
    ]);
    $backup = ScheduledDatabaseBackup::create([
        'team_id' => $this->rootTeam->id,
        'frequency' => '0 * * * *',
        'database_id' => $serviceDatabase->id,
        'database_type' => $serviceDatabase->getMorphClass(),
        'enabled' => true,
    ]);

    withIsolatedScheduledLogsForMonitoringTest(function (string $logPath) use ($backup, $project, $environment, $service, $serviceDatabase) {
        file_put_contents(
            $logPath,
            '['.now()->format('Y-m-d H:i:s').'] production.INFO: Backup skipped {"type":"backup","skip_reason":"server_not_functional","backup_id":'.$backup->id.',"team_id":'.$this->rootTeam->id.'}'."\n"
        );

        $expectedUrl = route('project.service.database.backups', [
            'project_uuid' => $project->uuid,
            'environment_uuid' => $environment->uuid,
            'service_uuid' => $service->uuid,
            'stack_service_uuid' => $serviceDatabase->uuid,
        ]);

        Livewire::test(ScheduledJobs::class)
            ->assertOk()
            ->assertSee('service-postgres')
            ->assertSeeHtml('href="'.$expectedUrl.'"');
    });
});

test('skipped standalone database backups keep standalone backup link', function () {
    $this->actingAs($this->rootUser);
    session(['currentTeam' => $this->rootTeam]);

    $server = Server::factory()->create(['team_id' => $this->rootTeam->id]);
    $destination = StandaloneDocker::where('server_id', $server->id)->firstOrFail();
    $project = Project::factory()->create(['team_id' => $this->rootTeam->id]);
    $environment = Environment::factory()->create(['project_id' => $project->id]);
    $database = StandalonePostgresql::create([
        'name' => 'standalone-postgres',
        'image' => 'postgres:16-alpine',
        'postgres_user' => 'postgres',
        'postgres_password' => 'password',
        'postgres_db' => 'postgres',
        'environment_id' => $environment->id,
        'destination_id' => $destination->id,
        'destination_type' => $destination->getMorphClass(),
    ]);
    $backup = ScheduledDatabaseBackup::create([
        'team_id' => $this->rootTeam->id,
        'frequency' => '0 * * * *',
        'database_id' => $database->id,
        'database_type' => $database->getMorphClass(),
        'enabled' => true,
    ]);

    withIsolatedScheduledLogsForMonitoringTest(function (string $logPath) use ($backup, $project, $environment, $database) {
        file_put_contents(
            $logPath,
            '['.now()->format('Y-m-d H:i:s').'] production.INFO: Backup skipped {"type":"backup","skip_reason":"server_not_functional","backup_id":'.$backup->id.',"team_id":'.$this->rootTeam->id.'}'."\n"
        );

        $expectedUrl = route('project.database.backup.index', [
            'project_uuid' => $project->uuid,
            'environment_uuid' => $environment->uuid,
            'database_uuid' => $database->uuid,
        ]);

        Livewire::test(ScheduledJobs::class)
            ->assertOk()
            ->assertSee('standalone-postgres')
            ->assertSeeHtml('href="'.$expectedUrl.'"');
    });
});
