<?php

declare(strict_types=1);

namespace App\Services\TenantReviews;

use App\Jobs\ComposeTenantReviewJob;
use App\Models\EvidenceSnapshot;
use App\Models\OperationRun;
use App\Models\Tenant;
use App\Models\TenantReview;
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\TenantReviewStatus;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;

final class TenantReviewService
{
    public function __construct(
        private readonly OperationRunService $operationRuns,
        private readonly WorkspaceAuditLogger $auditLogger,
        private readonly TenantReviewComposer $composer,
        private readonly TenantReviewFingerprint $fingerprint,
    ) {}

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

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

        if (! $tenant instanceof Tenant) {
            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::TenantReviewRefreshed,
        );
    }

    public function compose(TenantReview $review): TenantReview
    {
        $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,
                    'tenant_id' => (int) $review->tenant_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(Tenant $tenant): ?EvidenceSnapshot
    {
        return EvidenceSnapshot::query()
            ->forTenant((int) $tenant->getKey())
            ->current()
            ->latest('generated_at')
            ->latest('id')
            ->first();
    }

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

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

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

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

        if ((int) $snapshot->tenant_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 TenantReview) {
            $existing = $this->findExistingMutableReview($tenant, $fingerprint);

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

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

        $review ??= TenantReview::query()->create([
            'tenant_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' => TenantReviewStatus::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 TenantReview) {
            $existingReview->forceFill([
                'evidence_snapshot_id' => (int) $snapshot->getKey(),
                'operation_run_id' => (int) $operationRun->getKey(),
                'fingerprint' => $fingerprint,
                'status' => TenantReviewStatus::Draft->value,
            ])->save();

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

        if ($operationRun->wasRecentlyCreated) {
            $this->operationRuns->dispatchOrFail($operationRun, function () use ($review, $operationRun): void {
                ComposeTenantReviewJob::dispatch(
                    tenantReviewId: (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: 'tenant_review',
            resourceId: (string) $review->getKey(),
            targetLabel: sprintf('Tenant review #%d', (int) $review->getKey()),
            operationRunId: (int) $operationRun->getKey(),
            tenant: $tenant,
        );

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

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