<?php

declare(strict_types=1);

namespace App\Services\EnvironmentReviews;

use App\Jobs\ComposeEnvironmentReviewJob;
use App\Models\EvidenceSnapshot;
use App\Models\OperationRun;
use App\Models\ManagedEnvironment;
use App\Models\EnvironmentReview;
use App\Models\User;
use App\Services\Audit\WorkspaceAuditLogger;
use App\Services\OperationRunService;
use App\Support\Audit\AuditActionId;
use App\Support\OperationRunType;
use App\Support\EnvironmentReviewStatus;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;

final class EnvironmentReviewService
{
    public function __construct(
        private readonly OperationRunService $operationRuns,
        private readonly WorkspaceAuditLogger $auditLogger,
        private readonly EnvironmentReviewComposer $composer,
        private readonly EnvironmentReviewFingerprint $fingerprint,
    ) {}

    public function create(ManagedEnvironment $tenant, EvidenceSnapshot $snapshot, User $user): EnvironmentReview
    {
        return $this->queueComposition(
            tenant: $tenant,
            snapshot: $snapshot,
            user: $user,
            existingReview: null,
            auditAction: AuditActionId::EnvironmentReviewCreated,
        );
    }

    public function refresh(EnvironmentReview $review, User $user, ?EvidenceSnapshot $snapshot = null): EnvironmentReview
    {
        $tenant = $review->tenant;

        if (! $tenant instanceof ManagedEnvironment) {
            throw new InvalidArgumentException('Review tenant could not be resolved.');
        }

        $snapshot ??= $this->resolveLatestSnapshot($tenant) ?? $review->evidenceSnapshot;

        return $this->queueComposition(
            tenant: $tenant,
            snapshot: $snapshot,
            user: $user,
            existingReview: $review,
            auditAction: AuditActionId::EnvironmentReviewRefreshed,
        );
    }

    public function compose(EnvironmentReview $review): EnvironmentReview
    {
        $review->loadMissing(['tenant', 'evidenceSnapshot.items']);

        $snapshot = $review->evidenceSnapshot;

        if (! $snapshot instanceof EvidenceSnapshot) {
            throw new InvalidArgumentException('Review evidence snapshot could not be resolved.');
        }

        $payload = $this->composer->compose($snapshot, $review);

        DB::transaction(function () use ($review, $payload, $snapshot): void {
            $review->forceFill([
                'fingerprint' => $payload['fingerprint'],
                'completeness_state' => $payload['completeness_state'],
                'status' => $payload['status'],
                'summary' => $payload['summary'],
                'generated_at' => now(),
                'evidence_snapshot_id' => (int) $snapshot->getKey(),
            ])->save();

            $review->sections()->delete();

            foreach ($payload['sections'] as $section) {
                $review->sections()->create([
                    'workspace_id' => (int) $review->workspace_id,
                    'managed_environment_id' => (int) $review->managed_environment_id,
                    'section_key' => $section['section_key'],
                    'title' => $section['title'],
                    'sort_order' => $section['sort_order'],
                    'required' => $section['required'],
                    'completeness_state' => $section['completeness_state'],
                    'source_snapshot_fingerprint' => $section['source_snapshot_fingerprint'],
                    'summary_payload' => $section['summary_payload'],
                    'render_payload' => $section['render_payload'],
                    'measured_at' => $section['measured_at'],
                ]);
            }
        });

        return $review->refresh()->load(['tenant', 'evidenceSnapshot', 'sections', 'operationRun', 'initiator', 'publisher', 'currentExportReviewPack']);
    }

    public function resolveLatestSnapshot(ManagedEnvironment $tenant): ?EvidenceSnapshot
    {
        return EvidenceSnapshot::query()
            ->forTenant((int) $tenant->getKey())
            ->current()
            ->latest('generated_at')
            ->latest('id')
            ->first();
    }

    public function activeCompositionRun(ManagedEnvironment $tenant, ?EvidenceSnapshot $snapshot = null): ?OperationRun
    {
        $snapshot ??= $this->resolveLatestSnapshot($tenant);

        if (! $snapshot instanceof EvidenceSnapshot) {
            return null;
        }

        return $this->operationRuns->findCanonicalRunWithIdentity(
            tenant: $tenant,
            type: OperationRunType::EnvironmentReviewCompose->value,
            identityInputs: [
                'managed_environment_id' => (int) $tenant->getKey(),
                'snapshot_id' => (int) $snapshot->getKey(),
                'fingerprint' => $this->fingerprint->forSnapshot($tenant, $snapshot),
            ],
        );
    }

    private function queueComposition(
        ManagedEnvironment $tenant,
        ?EvidenceSnapshot $snapshot,
        User $user,
        ?EnvironmentReview $existingReview,
        AuditActionId $auditAction,
    ): EnvironmentReview {
        if (! $snapshot instanceof EvidenceSnapshot) {
            throw new InvalidArgumentException('An eligible evidence snapshot is required.');
        }

        if ((int) $snapshot->managed_environment_id !== (int) $tenant->getKey()) {
            throw new InvalidArgumentException('Evidence snapshot does not belong to the target tenant.');
        }

        $fingerprint = $this->fingerprint->forSnapshot($tenant, $snapshot);
        $review = $existingReview;

        if (! $review instanceof EnvironmentReview) {
            $existing = $this->findExistingMutableReview($tenant, $fingerprint);

            if ($existing instanceof EnvironmentReview) {
                return $existing->load(['tenant', 'evidenceSnapshot', 'sections', 'operationRun', 'initiator', 'publisher']);
            }
        }

        $operationRun = $this->operationRuns->ensureRunWithIdentity(
            tenant: $tenant,
            type: OperationRunType::EnvironmentReviewCompose->value,
            identityInputs: [
                'managed_environment_id' => (int) $tenant->getKey(),
                'snapshot_id' => (int) $snapshot->getKey(),
                'fingerprint' => $fingerprint,
            ],
            context: [
                'managed_environment_id' => (int) $tenant->getKey(),
                'workspace_id' => (int) $tenant->workspace_id,
                'evidence_snapshot_id' => (int) $snapshot->getKey(),
                'review_fingerprint' => $fingerprint,
                'review_id' => $existingReview?->getKey(),
                'progress' => [
                    'composite' => $this->reviewComposeProgressMetadata($snapshot),
                ],
            ],
            initiator: $user,
        );

        $review ??= EnvironmentReview::query()->create([
            'managed_environment_id' => (int) $tenant->getKey(),
            'workspace_id' => (int) $tenant->workspace_id,
            'evidence_snapshot_id' => (int) $snapshot->getKey(),
            'operation_run_id' => (int) $operationRun->getKey(),
            'initiated_by_user_id' => (int) $user->getKey(),
            'fingerprint' => $fingerprint,
            'status' => EnvironmentReviewStatus::Draft->value,
            'completeness_state' => (string) $snapshot->completeness_state,
            'summary' => [
                'evidence_basis' => [
                    'snapshot_id' => (int) $snapshot->getKey(),
                    'snapshot_fingerprint' => (string) $snapshot->fingerprint,
                    'snapshot_completeness_state' => (string) $snapshot->completeness_state,
                    'snapshot_generated_at' => $snapshot->generated_at?->toIso8601String(),
                ],
                'publish_blockers' => [],
                'has_ready_export' => false,
                'last_requested_at' => now()->toIso8601String(),
            ],
        ]);

        if ($existingReview instanceof EnvironmentReview) {
            $existingReview->forceFill([
                'evidence_snapshot_id' => (int) $snapshot->getKey(),
                'operation_run_id' => (int) $operationRun->getKey(),
                'fingerprint' => $fingerprint,
                'status' => EnvironmentReviewStatus::Draft->value,
            ])->save();

            $review = $existingReview->refresh();
        }

        if ($operationRun->wasRecentlyCreated) {
            $this->operationRuns->dispatchOrFail($operationRun, function () use ($review, $operationRun): void {
                ComposeEnvironmentReviewJob::dispatch(
                    environmentReviewId: (int) $review->getKey(),
                    operationRunId: (int) $operationRun->getKey(),
                );
            });
        }

        $this->auditLogger->log(
            workspace: $tenant->workspace,
            action: $auditAction,
            context: [
                'metadata' => [
                    'review_id' => (int) $review->getKey(),
                    'evidence_snapshot_id' => (int) $snapshot->getKey(),
                    'status' => (string) $review->status,
                    'fingerprint' => $fingerprint,
                ],
            ],
            actor: $user,
            resourceType: 'environment_review',
            resourceId: (string) $review->getKey(),
            targetLabel: sprintf('ManagedEnvironment review #%d', (int) $review->getKey()),
            operationRunId: (int) $operationRun->getKey(),
            tenant: $tenant,
        );

        return $review->load(['tenant', 'evidenceSnapshot', 'sections', 'operationRun', 'initiator', 'publisher']);
    }

    private function findExistingMutableReview(ManagedEnvironment $tenant, string $fingerprint): ?EnvironmentReview
    {
        return EnvironmentReview::query()
            ->forTenant((int) $tenant->getKey())
            ->mutable()
            ->where('fingerprint', $fingerprint)
            ->latest('id')
            ->first();
    }

    /**
     * @return array{label: string, operation_count: int, failed_count: int, partial_count: int}
     */
    private function reviewComposeProgressMetadata(EvidenceSnapshot $snapshot): array
    {
        $snapshot->loadMissing('items');

        $operationsSummary = $snapshot->items
            ->firstWhere('dimension_key', 'operations_summary')
            ?->summary_payload;

        $operationCount = is_numeric(data_get($operationsSummary, 'operation_count'))
            ? (int) data_get($operationsSummary, 'operation_count')
            : 0;
        $failedCount = is_numeric(data_get($operationsSummary, 'failed_count'))
            ? (int) data_get($operationsSummary, 'failed_count')
            : 0;
        $partialCount = is_numeric(data_get($operationsSummary, 'partial_count'))
            ? (int) data_get($operationsSummary, 'partial_count')
            : 0;

        return [
            'label' => $this->reviewComposeProgressLabel($operationCount, $failedCount, $partialCount),
            'operation_count' => $operationCount,
            'failed_count' => $failedCount,
            'partial_count' => $partialCount,
        ];
    }

    private function reviewComposeProgressLabel(int $operationCount, int $failedCount, int $partialCount): string
    {
        $baseLabel = $operationCount > 0
            ? sprintf('Review composition is aggregating %d %s.', $operationCount, $operationCount === 1 ? 'operation' : 'operations')
            : 'Review composition is aggregating related operations.';

        if ($failedCount > 0 && $partialCount > 0) {
            return sprintf(
                '%s %d %s and %d %s currently need review.',
                $baseLabel,
                $failedCount,
                $failedCount === 1 ? 'failed operation' : 'failed operations',
                $partialCount,
                $partialCount === 1 ? 'partial operation' : 'partial operations',
            );
        }

        if ($failedCount > 0) {
            return sprintf(
                '%s %d %s currently need review.',
                $baseLabel,
                $failedCount,
                $failedCount === 1 ? 'failed operation' : 'failed operations',
            );
        }

        if ($partialCount > 0) {
            return sprintf(
                '%s %d %s currently need review.',
                $baseLabel,
                $partialCount,
                $partialCount === 1 ? 'partial operation' : 'partial operations',
            );
        }

        return $baseLabel;
    }
}
