<?php

declare(strict_types=1);

use App\Models\AuditLog;
use App\Models\Finding;
use App\Models\ManagedEnvironment;
use App\Models\ManagedEnvironmentMembership;
use App\Models\User;
use App\Services\Findings\FindingAssignmentHygieneService;
use App\Support\Audit\AuditActionId;
use Carbon\CarbonImmutable;

afterEach(function (): void {
    CarbonImmutable::setTestNow();
});

function assignmentHygieneServiceContext(string $role = 'readonly', string $workspaceRole = 'readonly'): array
{
    $tenant = ManagedEnvironment::factory()->create(['status' => 'active']);
    [$user, $tenant] = createUserWithTenant($tenant, role: $role, workspaceRole: $workspaceRole);

    return [
        app(FindingAssignmentHygieneService::class),
        $user,
        $tenant->workspace()->firstOrFail(),
        $tenant,
    ];
}

function assignmentHygieneFinding(ManagedEnvironment $tenant, array $attributes = []): Finding
{
    return Finding::factory()->for($tenant)->create(array_merge([
        'workspace_id' => (int) $tenant->workspace_id,
        'status' => Finding::STATUS_TRIAGED,
        'subject_external_id' => fake()->uuid(),
    ], $attributes));
}

function recordAssignmentHygieneWorkflowAudit(Finding $finding, string $action, CarbonImmutable $recordedAt): AuditLog
{
    return AuditLog::query()->create([
        'workspace_id' => (int) $finding->workspace_id,
        'managed_environment_id' => (int) $finding->managed_environment_id,
        'action' => $action,
        'status' => 'success',
        'resource_type' => 'finding',
        'resource_id' => (string) $finding->getKey(),
        'summary' => 'Test workflow activity',
        'recorded_at' => $recordedAt,
    ]);
}

it('classifies broken assignments from current tenant entitlement loss and soft-deleted assignees', function (): void {
    [$service, $viewer, $workspace, $tenant] = assignmentHygieneServiceContext();

    $lostMember = User::factory()->create(['name' => 'Lost Member']);
    createUserWithTenant($tenant, $lostMember, role: 'readonly', workspaceRole: 'readonly');
    ManagedEnvironmentMembership::query()
        ->where('managed_environment_id', (int) $tenant->getKey())
        ->where('user_id', (int) $lostMember->getKey())
        ->delete();

    $softDeletedAssignee = User::factory()->create(['name' => 'Deleted Member']);
    createUserWithTenant($tenant, $softDeletedAssignee, role: 'readonly', workspaceRole: 'readonly');
    $softDeletedAssignee->delete();

    $healthyAssignee = User::factory()->create(['name' => 'Healthy Assignee']);
    createUserWithTenant($tenant, $healthyAssignee, role: 'readonly', workspaceRole: 'readonly');

    $brokenByMembership = assignmentHygieneFinding($tenant, [
        'owner_user_id' => (int) $viewer->getKey(),
        'assignee_user_id' => (int) $lostMember->getKey(),
        'status' => Finding::STATUS_TRIAGED,
        'subject_external_id' => 'broken-membership',
    ]);

    $brokenBySoftDelete = assignmentHygieneFinding($tenant, [
        'owner_user_id' => (int) $viewer->getKey(),
        'assignee_user_id' => (int) $softDeletedAssignee->getKey(),
        'status' => Finding::STATUS_NEW,
        'subject_external_id' => 'broken-soft-delete',
    ]);

    $healthyAssigned = assignmentHygieneFinding($tenant, [
        'owner_user_id' => (int) $viewer->getKey(),
        'assignee_user_id' => (int) $healthyAssignee->getKey(),
        'status' => Finding::STATUS_NEW,
        'subject_external_id' => 'healthy-assigned',
    ]);

    $ordinaryIntake = assignmentHygieneFinding($tenant, [
        'owner_user_id' => (int) $viewer->getKey(),
        'assignee_user_id' => null,
        'status' => Finding::STATUS_NEW,
        'subject_external_id' => 'ordinary-intake',
    ]);

    $issues = $service->issueQuery($workspace, $viewer)->get()->keyBy('id');
    $summary = $service->summary($workspace, $viewer);

    expect($issues->keys()->all())
        ->toContain((int) $brokenByMembership->getKey(), (int) $brokenBySoftDelete->getKey())
        ->not->toContain((int) $healthyAssigned->getKey(), (int) $ordinaryIntake->getKey())
        ->and($service->reasonLabelsFor($issues[$brokenByMembership->getKey()]))
        ->toBe(['Broken assignment'])
        ->and($service->reasonLabelsFor($issues[$brokenBySoftDelete->getKey()]))
        ->toBe(['Broken assignment'])
        ->and($issues[$brokenBySoftDelete->getKey()]->assigneeUser?->name)
        ->toBe('Deleted Member')
        ->and($summary)
        ->toBe([
            'unique_issue_count' => 2,
            'broken_assignment_count' => 2,
            'stale_in_progress_count' => 0,
        ]);
});

it('classifies stale in-progress work from meaningful workflow activity and excludes recently advanced or merely overdue work', function (): void {
    CarbonImmutable::setTestNow(CarbonImmutable::create(2026, 4, 22, 9, 0, 0, 'UTC'));

    [$service, $viewer, $workspace, $tenant] = assignmentHygieneServiceContext();

    $staleFinding = assignmentHygieneFinding($tenant, [
        'owner_user_id' => (int) $viewer->getKey(),
        'assignee_user_id' => (int) $viewer->getKey(),
        'status' => Finding::STATUS_IN_PROGRESS,
        'in_progress_at' => now()->subDays(10),
        'subject_external_id' => 'stale-finding',
    ]);
    recordAssignmentHygieneWorkflowAudit($staleFinding, AuditActionId::FindingInProgress->value, CarbonImmutable::now()->subDays(10));

    $recentlyAssigned = assignmentHygieneFinding($tenant, [
        'owner_user_id' => (int) $viewer->getKey(),
        'assignee_user_id' => (int) $viewer->getKey(),
        'status' => Finding::STATUS_IN_PROGRESS,
        'in_progress_at' => now()->subDays(10),
        'subject_external_id' => 'recently-assigned',
    ]);
    recordAssignmentHygieneWorkflowAudit($recentlyAssigned, AuditActionId::FindingAssigned->value, CarbonImmutable::now()->subDays(2));

    $recentlyReopened = assignmentHygieneFinding($tenant, [
        'owner_user_id' => (int) $viewer->getKey(),
        'assignee_user_id' => (int) $viewer->getKey(),
        'status' => Finding::STATUS_IN_PROGRESS,
        'in_progress_at' => now()->subDays(10),
        'reopened_at' => now()->subDay(),
        'subject_external_id' => 'recently-reopened',
    ]);
    recordAssignmentHygieneWorkflowAudit($recentlyReopened, AuditActionId::FindingReopened->value, CarbonImmutable::now()->subDay());

    $overdueButActive = assignmentHygieneFinding($tenant, [
        'owner_user_id' => (int) $viewer->getKey(),
        'assignee_user_id' => (int) $viewer->getKey(),
        'status' => Finding::STATUS_IN_PROGRESS,
        'in_progress_at' => now()->subDays(10),
        'due_at' => now()->subDay(),
        'subject_external_id' => 'overdue-but-active',
    ]);
    recordAssignmentHygieneWorkflowAudit($overdueButActive, AuditActionId::FindingAssigned->value, CarbonImmutable::now()->subHours(12));

    $issues = $service->issueQuery(
        $workspace,
        $viewer,
        reasonFilter: FindingAssignmentHygieneService::REASON_STALE_IN_PROGRESS,
    )->get();

    $summary = $service->summary($workspace, $viewer);

    expect($issues->pluck('id')->all())
        ->toBe([(int) $staleFinding->getKey()])
        ->and($service->reasonLabelsFor($issues->firstOrFail()))
        ->toBe(['Stale in progress'])
        ->and($service->lastWorkflowActivityAt($issues->firstOrFail())?->toIso8601String())
        ->toBe(CarbonImmutable::now()->subDays(10)->toIso8601String())
        ->and($summary)
        ->toBe([
            'unique_issue_count' => 1,
            'broken_assignment_count' => 0,
            'stale_in_progress_count' => 1,
        ]);
});

it('counts multi-reason findings once while excluding healthy assigned work and ordinary intake backlog', function (): void {
    CarbonImmutable::setTestNow(CarbonImmutable::create(2026, 4, 22, 9, 0, 0, 'UTC'));

    [$service, $viewer, $workspace, $tenant] = assignmentHygieneServiceContext();

    $lostMember = User::factory()->create(['name' => 'Lost Worker']);
    createUserWithTenant($tenant, $lostMember, role: 'readonly', workspaceRole: 'readonly');
    ManagedEnvironmentMembership::query()
        ->where('managed_environment_id', (int) $tenant->getKey())
        ->where('user_id', (int) $lostMember->getKey())
        ->delete();

    $brokenAndStale = assignmentHygieneFinding($tenant, [
        'owner_user_id' => (int) $viewer->getKey(),
        'assignee_user_id' => (int) $lostMember->getKey(),
        'status' => Finding::STATUS_IN_PROGRESS,
        'in_progress_at' => now()->subDays(8),
        'subject_external_id' => 'broken-and-stale',
    ]);
    recordAssignmentHygieneWorkflowAudit($brokenAndStale, AuditActionId::FindingInProgress->value, CarbonImmutable::now()->subDays(8));

    assignmentHygieneFinding($tenant, [
        'owner_user_id' => (int) $viewer->getKey(),
        'assignee_user_id' => (int) $viewer->getKey(),
        'status' => Finding::STATUS_TRIAGED,
        'subject_external_id' => 'healthy-assigned',
    ]);

    assignmentHygieneFinding($tenant, [
        'owner_user_id' => (int) $viewer->getKey(),
        'assignee_user_id' => null,
        'status' => Finding::STATUS_NEW,
        'subject_external_id' => 'ordinary-intake',
    ]);

    $issues = $service->issueQuery($workspace, $viewer)->get();
    $summary = $service->summary($workspace, $viewer);

    expect($issues)->toHaveCount(1)
        ->and((int) $issues->firstOrFail()->getKey())->toBe((int) $brokenAndStale->getKey())
        ->and($service->reasonLabelsFor($issues->firstOrFail()))
        ->toBe(['Broken assignment', 'Stale in progress'])
        ->and($summary)
        ->toBe([
            'unique_issue_count' => 1,
            'broken_assignment_count' => 1,
            'stale_in_progress_count' => 1,
        ]);
});
