<?php

declare(strict_types=1);

use App\Filament\Pages\Settings\WorkspaceSettings;
use App\Models\AuditLog;
use App\Models\User;
use App\Models\Workspace;
use App\Models\WorkspaceMembership;
use App\Models\WorkspaceSetting;
use App\Services\Settings\SettingsResolver;
use App\Services\Settings\SettingsWriter;
use App\Support\Audit\AuditActionId;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Actions\Testing\TestAction;
use Illuminate\Validation\ValidationException;
use Livewire\Livewire;

/**
 * @return array{0: Workspace, 1: User}
 */
function workspaceManagerUser(): array
{
    $workspace = Workspace::factory()->create();
    $user = User::factory()->create();

    WorkspaceMembership::factory()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'user_id' => (int) $user->getKey(),
        'role' => 'manager',
    ]);

    session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());

    return [$workspace, $user];
}

it('allows workspace managers to save and reset workspace slice settings', function (): void {
    [$workspace, $user] = workspaceManagerUser();

    $this->actingAs($user)
        ->get(WorkspaceSettings::getUrl(panel: 'admin'))
        ->assertSuccessful();

    $component = Livewire::actingAs($user)
        ->test(WorkspaceSettings::class)
        ->assertSet('data.ai_policy_mode', null)
        ->assertSet('data.backup_retention_keep_last_default', null)
        ->assertSet('data.backup_retention_min_floor', null)
        ->assertSet('data.drift_severity_mapping', [])
        ->assertSet('data.baseline_severity_missing_policy', null)
        ->assertSet('data.baseline_severity_different_version', null)
        ->assertSet('data.baseline_severity_unexpected_policy', null)
        ->assertSet('data.baseline_alert_min_severity', null)
        ->assertSet('data.baseline_auto_close_enabled', null)
        ->assertSet('data.findings_sla_critical', null)
        ->assertSet('data.findings_sla_high', null)
        ->assertSet('data.findings_sla_medium', null)
        ->assertSet('data.findings_sla_low', null)
        ->assertSet('data.operations_operation_run_retention_days', null)
        ->assertSet('data.operations_stuck_run_threshold_minutes', null)
        ->set('data.ai_policy_mode', 'private_only')
        ->set('data.backup_retention_keep_last_default', 55)
        ->set('data.backup_retention_min_floor', 12)
        ->set('data.drift_severity_mapping', ['drift' => 'critical'])
        ->set('data.baseline_severity_missing_policy', 'critical')
        ->set('data.baseline_severity_different_version', 'low')
        ->set('data.baseline_severity_unexpected_policy', 'medium')
        ->set('data.baseline_alert_min_severity', 'critical')
        ->set('data.baseline_auto_close_enabled', '0')
        ->set('data.findings_sla_critical', 2)
        ->set('data.findings_sla_high', 5)
        ->set('data.findings_sla_medium', 10)
        ->set('data.findings_sla_low', 20)
        ->set('data.operations_operation_run_retention_days', 120)
        ->set('data.operations_stuck_run_threshold_minutes', 60)
        ->callAction('save')
        ->assertHasNoErrors()
        ->assertSet('data.ai_policy_mode', 'private_only')
        ->assertSet('data.backup_retention_keep_last_default', 55)
        ->assertSet('data.backup_retention_min_floor', 12)
        ->assertSet('data.baseline_severity_missing_policy', 'critical')
        ->assertSet('data.baseline_severity_different_version', 'low')
        ->assertSet('data.baseline_severity_unexpected_policy', 'medium')
        ->assertSet('data.baseline_alert_min_severity', 'critical')
        ->assertSet('data.baseline_auto_close_enabled', '0')
        ->assertSet('data.findings_sla_critical', 2)
        ->assertSet('data.findings_sla_high', 5)
        ->assertSet('data.findings_sla_medium', 10)
        ->assertSet('data.findings_sla_low', 20)
        ->assertSet('data.operations_operation_run_retention_days', 120)
        ->assertSet('data.operations_stuck_run_threshold_minutes', 60);

    expect(WorkspaceSetting::query()
        ->where('workspace_id', (int) $workspace->getKey())
        ->where('domain', 'backup')
        ->where('key', 'retention_keep_last_default')
        ->exists())->toBeTrue();

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'backup', 'retention_keep_last_default'))
        ->toBe(55);

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'ai', 'policy_mode'))
        ->toBe('private_only');

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'backup', 'retention_min_floor'))
        ->toBe(12);

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'drift', 'severity_mapping'))
        ->toBe(['drift' => 'critical']);

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'baseline', 'severity_mapping'))
        ->toBe([
            'different_version' => 'low',
            'missing_policy' => 'critical',
            'unexpected_policy' => 'medium',
        ]);

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'baseline', 'alert_min_severity'))
        ->toBe('critical');

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'baseline', 'auto_close_enabled'))
        ->toBeFalse();

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'findings', 'sla_days'))
        ->toBe([
            'critical' => 2,
            'high' => 5,
            'medium' => 10,
            'low' => 20,
        ]);

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'operations', 'operation_run_retention_days'))
        ->toBe(120);

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'operations', 'stuck_run_threshold_minutes'))
        ->toBe(60);

    $component
        ->set('data.backup_retention_keep_last_default', '')
        ->callAction('save')
        ->assertHasNoErrors()
        ->assertSet('data.backup_retention_keep_last_default', null);

    expect(WorkspaceSetting::query()
        ->where('workspace_id', (int) $workspace->getKey())
        ->where('domain', 'backup')
        ->where('key', 'retention_keep_last_default')
        ->exists())->toBeFalse();

    $component
        ->mountFormComponentAction('ai_policy_mode', 'reset_ai_policy_mode', [], 'content')
        ->callMountedFormComponentAction()
        ->assertHasNoErrors()
        ->assertSet('data.ai_policy_mode', null);

    expect(WorkspaceSetting::query()
        ->where('workspace_id', (int) $workspace->getKey())
        ->where('domain', 'ai')
        ->where('key', 'policy_mode')
        ->exists())->toBeFalse();

    $component
        ->mountFormComponentAction('operations_operation_run_retention_days', 'reset_operations_operation_run_retention_days', [], 'content')
        ->callMountedFormComponentAction()
        ->assertHasNoErrors()
        ->assertSet('data.operations_operation_run_retention_days', null);

    expect(WorkspaceSetting::query()
        ->where('workspace_id', (int) $workspace->getKey())
        ->where('domain', 'operations')
        ->where('key', 'operation_run_retention_days')
        ->exists())->toBeFalse();
});

it('treats an empty KeyValue row as unset and still allows saving other fields', function (): void {
    [$workspace, $user] = workspaceManagerUser();

    $this->actingAs($user)
        ->get(WorkspaceSettings::getUrl(panel: 'admin'))
        ->assertSuccessful();

    Livewire::actingAs($user)
        ->test(WorkspaceSettings::class)
        ->set('data.drift_severity_mapping', ['' => ''])
        ->set('data.backup_retention_keep_last_default', 10)
        ->callAction('save')
        ->assertHasNoErrors();

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'drift', 'severity_mapping'))
        ->toBe([]);

    expect(WorkspaceSetting::query()
        ->where('workspace_id', (int) $workspace->getKey())
        ->where('domain', 'drift')
        ->where('key', 'severity_mapping')
        ->exists())->toBeFalse();

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'backup', 'retention_keep_last_default'))
        ->toBe(10);
});

it('accepts Filament KeyValue row-shaped state when saving drift severity mapping', function (): void {
    [$workspace, $user] = workspaceManagerUser();

    $this->actingAs($user)
        ->get(WorkspaceSettings::getUrl(panel: 'admin'))
        ->assertSuccessful();

    Livewire::actingAs($user)
        ->test(WorkspaceSettings::class)
        ->set('data.drift_severity_mapping', [
            ['key' => 'drift', 'value' => 'critical'],
            ['key' => '', 'value' => ''],
        ])
        ->callAction('save')
        ->assertHasNoErrors();

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'drift', 'severity_mapping'))
        ->toBe(['drift' => 'critical']);
});

it('clearing a KeyValue mapping via an empty row resets the existing override', function (): void {
    [$workspace, $user] = workspaceManagerUser();

    WorkspaceSetting::query()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'domain' => 'drift',
        'key' => 'severity_mapping',
        'value' => ['drift' => 'low'],
        'updated_by_user_id' => (int) $user->getKey(),
    ]);

    Livewire::actingAs($user)
        ->test(WorkspaceSettings::class)
        ->assertSet('data.drift_severity_mapping', ['drift' => 'low'])
        ->set('data.drift_severity_mapping', ['' => ''])
        ->callAction('save')
        ->assertHasNoErrors();

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'drift', 'severity_mapping'))
        ->toBe([]);

    expect(WorkspaceSetting::query()
        ->where('workspace_id', (int) $workspace->getKey())
        ->where('domain', 'drift')
        ->where('key', 'severity_mapping')
        ->exists())->toBeFalse();
});

it('rejects unknown setting keys and does not persist or audit changes', function (): void {
    [$workspace, $user] = workspaceManagerUser();

    $writer = app(SettingsWriter::class);

    expect(fn () => $writer->updateWorkspaceSetting($user, $workspace, 'backup', 'unknown_setting_key', 25))
        ->toThrow(ValidationException::class);

    expect(WorkspaceSetting::query()->count())->toBe(0);
    expect(AuditLog::query()->count())->toBe(0);
});

it('rejects invalid backup settings bounds and does not persist or audit changes', function (): void {
    [$workspace, $user] = workspaceManagerUser();

    $writer = app(SettingsWriter::class);

    expect(fn () => $writer->updateWorkspaceSetting($user, $workspace, 'backup', 'retention_keep_last_default', 'not-an-integer'))
        ->toThrow(ValidationException::class);

    expect(fn () => $writer->updateWorkspaceSetting($user, $workspace, 'backup', 'retention_keep_last_default', 0))
        ->toThrow(ValidationException::class);

    expect(fn () => $writer->updateWorkspaceSetting($user, $workspace, 'backup', 'retention_keep_last_default', 366))
        ->toThrow(ValidationException::class);

    expect(fn () => $writer->updateWorkspaceSetting($user, $workspace, 'backup', 'retention_min_floor', 0))
        ->toThrow(ValidationException::class);

    expect(fn () => $writer->updateWorkspaceSetting($user, $workspace, 'backup', 'retention_min_floor', 366))
        ->toThrow(ValidationException::class);

    expect(WorkspaceSetting::query()->count())->toBe(0);
    expect(AuditLog::query()->count())->toBe(0);
});

it('rejects malformed drift severity mapping values on save', function (): void {
    [$workspace, $user] = workspaceManagerUser();

    $this->actingAs($user)
        ->get(WorkspaceSettings::getUrl(panel: 'admin'))
        ->assertSuccessful();

    Livewire::actingAs($user)
        ->test(WorkspaceSettings::class)
        ->set('data.drift_severity_mapping', ['drift' => 'urgent'])
        ->callAction('save')
        ->assertHasErrors(['data.drift_severity_mapping']);

    expect(WorkspaceSetting::query()
        ->where('workspace_id', (int) $workspace->getKey())
        ->where('domain', 'drift')
        ->where('key', 'severity_mapping')
        ->exists())->toBeFalse();
});

it('rejects malformed baseline settings values', function (): void {
    [$workspace, $user] = workspaceManagerUser();

    $writer = app(SettingsWriter::class);

    expect(fn () => $writer->updateWorkspaceSetting(
        actor: $user,
        workspace: $workspace,
        domain: 'baseline',
        key: 'severity_mapping',
        value: ['unknown_change_type' => 'high'],
    ))->toThrow(ValidationException::class);

    expect(fn () => $writer->updateWorkspaceSetting(
        actor: $user,
        workspace: $workspace,
        domain: 'baseline',
        key: 'severity_mapping',
        value: ['missing_policy' => 'urgent'],
    ))->toThrow(ValidationException::class);

    expect(fn () => $writer->updateWorkspaceSetting(
        actor: $user,
        workspace: $workspace,
        domain: 'baseline',
        key: 'alert_min_severity',
        value: 'urgent',
    ))->toThrow(ValidationException::class);

    expect(WorkspaceSetting::query()
        ->where('workspace_id', (int) $workspace->getKey())
        ->where('domain', 'baseline')
        ->exists())->toBeFalse();
});

it('saves partial findings sla days without auto-filling unset severities', function (): void {
    [$workspace, $user] = workspaceManagerUser();

    $this->actingAs($user)
        ->get(WorkspaceSettings::getUrl(panel: 'admin'))
        ->assertSuccessful();

    Livewire::actingAs($user)
        ->test(WorkspaceSettings::class)
        ->set('data.findings_sla_critical', 2)
        ->callAction('save')
        ->assertHasNoErrors()
        ->assertSet('data.findings_sla_critical', 2)
        ->assertSet('data.findings_sla_high', null)
        ->assertSet('data.findings_sla_medium', null)
        ->assertSet('data.findings_sla_low', null);

    $stored = WorkspaceSetting::query()
        ->where('workspace_id', (int) $workspace->getKey())
        ->where('domain', 'findings')
        ->where('key', 'sla_days')
        ->first();

    expect($stored)->not->toBeNull();

    $storedValue = $stored->getAttribute('value');
    expect($storedValue)->toBe(['critical' => 2]);

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'findings', 'sla_days'))
        ->toBe([
            'critical' => 2,
            'high' => 7,
            'medium' => 14,
            'low' => 30,
        ]);
});

it('rejects invalid drift severity mapping shape and values', function (): void {
    [$workspace, $user] = workspaceManagerUser();

    $writer = app(SettingsWriter::class);

    expect(fn () => $writer->updateWorkspaceSetting(
        actor: $user,
        workspace: $workspace,
        domain: 'drift',
        key: 'severity_mapping',
        value: [123 => 'low'],
    ))->toThrow(ValidationException::class);

    expect(fn () => $writer->updateWorkspaceSetting(
        actor: $user,
        workspace: $workspace,
        domain: 'drift',
        key: 'severity_mapping',
        value: ['drift' => 'urgent'],
    ))->toThrow(ValidationException::class);

    $writer->updateWorkspaceSetting(
        actor: $user,
        workspace: $workspace,
        domain: 'drift',
        key: 'severity_mapping',
        value: ['drift' => 'CRITICAL'],
    );

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'drift', 'severity_mapping'))
        ->toBe(['drift' => 'critical']);
});

it('saves and resets operations settings keys', function (): void {
    [$workspace, $user] = workspaceManagerUser();

    $this->actingAs($user)
        ->get(WorkspaceSettings::getUrl(panel: 'admin'))
        ->assertSuccessful();

    Livewire::actingAs($user)
        ->test(WorkspaceSettings::class)
        ->set('data.operations_operation_run_retention_days', 365)
        ->set('data.operations_stuck_run_threshold_minutes', 45)
        ->callAction('save')
        ->assertHasNoErrors()
        ->assertSet('data.operations_operation_run_retention_days', 365)
        ->assertSet('data.operations_stuck_run_threshold_minutes', 45)
        ->mountFormComponentAction('operations_stuck_run_threshold_minutes', 'reset_operations_stuck_run_threshold_minutes', [], 'content')
        ->callMountedFormComponentAction()
        ->assertHasNoErrors()
        ->assertSet('data.operations_stuck_run_threshold_minutes', null);

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'operations', 'operation_run_retention_days'))
        ->toBe(365);

    expect(app(SettingsResolver::class)->resolveValue($workspace, 'operations', 'stuck_run_threshold_minutes'))
        ->toBe(0);
});

it('requires confirmation for each per-setting reset action', function (): void {
    [$workspace, $user] = workspaceManagerUser();

    WorkspaceSetting::query()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'domain' => 'backup',
        'key' => 'retention_keep_last_default',
        'value' => 40,
        'updated_by_user_id' => (int) $user->getKey(),
    ]);

    WorkspaceSetting::query()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'domain' => 'backup',
        'key' => 'retention_min_floor',
        'value' => 5,
        'updated_by_user_id' => (int) $user->getKey(),
    ]);

    WorkspaceSetting::query()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'domain' => 'drift',
        'key' => 'severity_mapping',
        'value' => ['drift' => 'low'],
        'updated_by_user_id' => (int) $user->getKey(),
    ]);

    WorkspaceSetting::query()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'domain' => 'operations',
        'key' => 'operation_run_retention_days',
        'value' => 120,
        'updated_by_user_id' => (int) $user->getKey(),
    ]);

    WorkspaceSetting::query()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'domain' => 'operations',
        'key' => 'stuck_run_threshold_minutes',
        'value' => 30,
        'updated_by_user_id' => (int) $user->getKey(),
    ]);

    WorkspaceSetting::query()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'domain' => 'findings',
        'key' => 'sla_days',
        'value' => [
            'critical' => 3,
            'high' => 7,
            'medium' => 14,
            'low' => 30,
        ],
        'updated_by_user_id' => (int) $user->getKey(),
    ]);

    $component = Livewire::actingAs($user)->test(WorkspaceSettings::class);

    $component
        ->mountFormComponentAction('backup_retention_keep_last_default', 'reset_backup_retention_keep_last_default', [], 'content');
    expect($component->instance()->getMountedAction()?->isConfirmationRequired())->toBeTrue();
    $component->unmountFormComponentAction();

    $component
        ->mountFormComponentAction('backup_retention_min_floor', 'reset_backup_retention_min_floor', [], 'content');
    expect($component->instance()->getMountedAction()?->isConfirmationRequired())->toBeTrue();
    $component->unmountFormComponentAction();

    $component
        ->mountFormComponentAction('drift_severity_mapping', 'reset_drift_severity_mapping', [], 'content');
    expect($component->instance()->getMountedAction()?->isConfirmationRequired())->toBeTrue();
    $component->unmountFormComponentAction();

    $component
        ->mountAction(TestAction::make('reset_findings_sla_days')->schemaComponent('findings_section'));
    expect($component->instance()->getMountedAction()?->isConfirmationRequired())->toBeTrue();
    $component->unmountAction();

    $component
        ->mountFormComponentAction('operations_operation_run_retention_days', 'reset_operations_operation_run_retention_days', [], 'content');
    expect($component->instance()->getMountedAction()?->isConfirmationRequired())->toBeTrue();
    $component->unmountFormComponentAction();

    $component
        ->mountFormComponentAction('operations_stuck_run_threshold_minutes', 'reset_operations_stuck_run_threshold_minutes', [], 'content');
    expect($component->instance()->getMountedAction()?->isConfirmationRequired())->toBeTrue();
    $component->unmountFormComponentAction();
});

it('emits one audit entry per key changed when saving multiple settings at once', function (): void {
    [$workspace, $user] = workspaceManagerUser();

    $this->actingAs($user)
        ->get(WorkspaceSettings::getUrl(panel: 'admin'))
        ->assertSuccessful();

    Livewire::actingAs($user)
        ->test(WorkspaceSettings::class)
        ->set('data.backup_retention_keep_last_default', 50)
        ->set('data.backup_retention_min_floor', 10)
        ->set('data.operations_operation_run_retention_days', 120)
        ->callAction('save')
        ->assertHasNoErrors();

    $updatedEvents = AuditLog::query()
        ->where('workspace_id', (int) $workspace->getKey())
        ->where('action', AuditActionId::WorkspaceSettingUpdated->value)
        ->get();

    expect($updatedEvents)->toHaveCount(3);

    $keys = $updatedEvents
        ->map(fn (AuditLog $auditLog): ?string => data_get($auditLog->metadata, 'key'))
        ->filter(fn (?string $key): bool => is_string($key))
        ->values()
        ->all();

    expect($keys)->toEqualCanonicalizing([
        'retention_keep_last_default',
        'retention_min_floor',
        'operation_run_retention_days',
    ]);
});
