<?php

declare(strict_types=1);

namespace App\Services\SystemConsole;

use App\Models\OperationRun;
use App\Models\PlatformUser;
use App\Services\OperationRunService;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use InvalidArgumentException;

final class OperationRunTriageService
{
    private const RETRYABLE_TYPES = [
        'inventory.sync',
        'policy.sync',
        'directory.groups.sync',
        'rbac.health_check',
        'entra.admin_roles.scan',
        'environment.review_pack.generate',
        'environment.review.compose',
    ];

    private const CANCELABLE_TYPES = [
        'inventory.sync',
        'policy.sync',
        'directory.groups.sync',
        'rbac.health_check',
        'entra.admin_roles.scan',
        'environment.review_pack.generate',
        'environment.review.compose',
    ];

    public function __construct(
        private readonly OperationRunService $operationRunService,
        private readonly SystemConsoleAuditLogger $auditLogger,
    ) {}

    public function canRetry(OperationRun $run): bool
    {
        return (string) $run->status === OperationRunStatus::Completed->value
            && (string) $run->outcome === OperationRunOutcome::Failed->value
            && in_array($run->canonicalOperationType(), self::RETRYABLE_TYPES, true);
    }

    public function canCancel(OperationRun $run): bool
    {
        return in_array((string) $run->status, [
            OperationRunStatus::Queued->value,
            OperationRunStatus::Running->value,
        ], true)
            && in_array($run->canonicalOperationType(), self::CANCELABLE_TYPES, true);
    }

    public function retry(OperationRun $run, PlatformUser $actor): OperationRun
    {
        if (! $this->canRetry($run)) {
            throw new InvalidArgumentException('Operation run is not retryable.');
        }

        $context = is_array($run->context) ? $run->context : [];
        $context['triage'] = array_merge(
            is_array($context['triage'] ?? null) ? $context['triage'] : [],
            [
                'retry_of_run_id' => (int) $run->getKey(),
                'retried_at' => now()->toISOString(),
                'retried_by' => [
                    'platform_user_id' => (int) $actor->getKey(),
                    'name' => $actor->name,
                    'email' => $actor->email,
                ],
            ],
        );

        $retryRun = OperationRun::query()->create([
            'workspace_id' => (int) $run->workspace_id,
            'managed_environment_id' => $run->managed_environment_id !== null ? (int) $run->managed_environment_id : null,
            'user_id' => null,
            'initiator_name' => $actor->name ?? 'Platform operator',
            'type' => $run->canonicalOperationType(),
            'status' => OperationRunStatus::Queued->value,
            'outcome' => OperationRunOutcome::Pending->value,
            'run_identity_hash' => hash('sha256', 'retry|'.$run->getKey().'|'.now()->format('U.u').'|'.bin2hex(random_bytes(8))),
            'summary_counts' => [],
            'failure_summary' => [],
            'context' => $context,
            'started_at' => null,
            'completed_at' => null,
        ]);

        $this->auditLogger->log(
            actor: $actor,
            action: 'platform.system_console.retry',
            metadata: [
                'source_run_id' => (int) $run->getKey(),
                'new_run_id' => (int) $retryRun->getKey(),
                'operation_type' => $run->canonicalOperationType(),
            ],
            run: $retryRun,
        );

        return $retryRun;
    }

    public function cancel(OperationRun $run, PlatformUser $actor, string $reason): OperationRun
    {
        if (! $this->canCancel($run)) {
            throw new InvalidArgumentException('Operation run is not cancelable.');
        }

        $reason = $this->validatedReason($reason, 'reason');
        $context = is_array($run->context) ? $run->context : [];
        $context['triage'] = array_merge(
            is_array($context['triage'] ?? null) ? $context['triage'] : [],
            [
                'cancelled_at' => now()->toISOString(),
                'cancel_reason' => $reason,
                'cancelled_by' => [
                    'platform_user_id' => (int) $actor->getKey(),
                    'name' => $actor->name,
                    'email' => $actor->email,
                ],
            ],
        );

        $run->update([
            'context' => $context,
        ]);

        $run->refresh();

        $cancelledRun = $this->operationRunService->updateRun(
            $run,
            status: OperationRunStatus::Completed->value,
            outcome: OperationRunOutcome::Failed->value,
            failures: [
                [
                    'code' => 'run.cancelled',
                    'message' => 'Run cancelled by platform operator triage action.',
                    'reason' => $reason,
                ],
            ],
        );

        $this->auditLogger->log(
            actor: $actor,
            action: 'platform.system_console.cancel',
            metadata: [
                'operation_type' => $run->canonicalOperationType(),
                'reason' => $reason,
            ],
            run: $cancelledRun,
        );

        return $cancelledRun;
    }

    public function markInvestigated(OperationRun $run, PlatformUser $actor, string $reason): OperationRun
    {
        $reason = $this->validatedReason($reason, 'reason');

        $context = is_array($run->context) ? $run->context : [];
        $context['triage'] = array_merge(
            is_array($context['triage'] ?? null) ? $context['triage'] : [],
            [
                'investigated' => [
                    'reason' => $reason,
                    'investigated_at' => now()->toISOString(),
                    'investigated_by' => [
                        'platform_user_id' => (int) $actor->getKey(),
                        'name' => $actor->name,
                        'email' => $actor->email,
                    ],
                ],
            ],
        );

        $run->update([
            'context' => $context,
        ]);

        $run->refresh();

        $this->auditLogger->log(
            actor: $actor,
            action: 'platform.system_console.mark_investigated',
            metadata: [
                'reason' => $reason,
                'operation_type' => $run->canonicalOperationType(),
            ],
            run: $run,
        );

        return $run;
    }

    private function validatedReason(string $reason, string $field): string
    {
        $reason = trim($reason);

        if (mb_strlen($reason) < 5 || mb_strlen($reason) > 500) {
            throw new InvalidArgumentException(sprintf('%s must be between 5 and 500 characters.', $field));
        }

        return $reason;
    }
}
