<?php

declare(strict_types=1);

namespace App\Services\TenantReviews;

use App\Models\EvidenceSnapshot;
use App\Models\ReviewPack;
use App\Models\Tenant;
use App\Models\TenantReview;
use App\Models\User;
use App\Services\Audit\WorkspaceAuditLogger;
use App\Support\Audit\AuditActionId;
use App\Support\TenantReviewStatus;
use App\Support\Ui\DerivedState\DerivedStateFamily;
use App\Support\Ui\DerivedState\RequestScopedDerivedStateStore;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;

final class TenantReviewLifecycleService
{
    public function __construct(
        private readonly TenantReviewReadinessGate $readinessGate,
        private readonly TenantReviewService $reviewService,
        private readonly WorkspaceAuditLogger $auditLogger,
        private readonly RequestScopedDerivedStateStore $derivedStateStore,
    ) {}

    public function publish(TenantReview $review, User $user, string $reason): TenantReview
    {
        $review->loadMissing(['tenant', 'sections', 'currentExportReviewPack']);
        $tenant = $review->tenant;
        $reason = $this->validatedReason($reason, 'publish_reason');

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

        $blockers = $this->readinessGate->blockersForReview($review);
        $beforeStatus = (string) $review->status;

        if ($blockers !== []) {
            throw new InvalidArgumentException(implode(' ', $blockers));
        }

        $review->forceFill([
            'status' => TenantReviewStatus::Published->value,
            'published_at' => now(),
            'published_by_user_id' => (int) $user->getKey(),
            'summary' => array_merge(is_array($review->summary) ? $review->summary : [], [
                'publish_blockers' => [],
            ]),
        ])->save();

        $this->auditLogger->log(
            workspace: $tenant->workspace,
            action: AuditActionId::TenantReviewPublished,
            context: [
                'metadata' => [
                    'review_id' => (int) $review->getKey(),
                    'before_status' => $beforeStatus,
                    'after_status' => TenantReviewStatus::Published->value,
                    'reason' => $reason,
                ],
            ],
            actor: $user,
            resourceType: 'tenant_review',
            resourceId: (string) $review->getKey(),
            targetLabel: sprintf('Tenant review #%d', (int) $review->getKey()),
            tenant: $tenant,
        );

        $this->invalidateArtifactTruthCache($review);

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

    public function archive(TenantReview $review, User $user, string $reason): TenantReview
    {
        $review->loadMissing('tenant');
        $tenant = $review->tenant;
        $reason = $this->validatedReason($reason, 'archive_reason');

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

        $beforeStatus = (string) $review->status;

        if ($review->statusEnum()->isTerminal()) {
            return $review;
        }

        $review->forceFill([
            'status' => TenantReviewStatus::Archived->value,
            'archived_at' => now(),
        ])->save();

        $this->auditLogger->log(
            workspace: $tenant->workspace,
            action: AuditActionId::TenantReviewArchived,
            context: [
                'metadata' => [
                    'review_id' => (int) $review->getKey(),
                    'before_status' => $beforeStatus,
                    'after_status' => TenantReviewStatus::Archived->value,
                    'reason' => $reason,
                ],
            ],
            actor: $user,
            resourceType: 'tenant_review',
            resourceId: (string) $review->getKey(),
            targetLabel: sprintf('Tenant review #%d', (int) $review->getKey()),
            tenant: $tenant,
        );

        $this->invalidateArtifactTruthCache($review);

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

    public function createNextReview(TenantReview $review, User $user, ?EvidenceSnapshot $snapshot = null): TenantReview
    {
        $review->loadMissing(['tenant', 'evidenceSnapshot']);
        $tenant = $review->tenant;

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

        if (! $review->isPublished()) {
            throw new InvalidArgumentException('Only published reviews can start the next cycle.');
        }

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

        if (! $snapshot instanceof EvidenceSnapshot) {
            throw new InvalidArgumentException('An eligible evidence snapshot is required to create the next review.');
        }

        $nextReview = DB::transaction(function () use ($review, $user, $snapshot, $tenant): TenantReview {
            $nextReview = $this->reviewService->create($tenant, $snapshot, $user);

            if ((int) $nextReview->getKey() !== (int) $review->getKey()) {
                $review->forceFill([
                    'status' => TenantReviewStatus::Superseded->value,
                    'superseded_by_review_id' => (int) $nextReview->getKey(),
                ])->save();
            }

            $this->auditLogger->log(
                workspace: $tenant->workspace,
                action: AuditActionId::TenantReviewSuccessorCreated,
                context: [
                    'metadata' => [
                        'review_id' => (int) $review->getKey(),
                        'next_review_id' => (int) $nextReview->getKey(),
                        'before_status' => TenantReviewStatus::Published->value,
                        'after_status' => TenantReviewStatus::Superseded->value,
                    ],
                ],
                actor: $user,
                resourceType: 'tenant_review',
                resourceId: (string) $nextReview->getKey(),
                targetLabel: sprintf('Tenant review #%d', (int) $nextReview->getKey()),
                tenant: $tenant,
            );

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

        $this->invalidateArtifactTruthCache($review);
        $this->invalidateArtifactTruthCache($nextReview);

        return $nextReview;
    }

    private function validatedReason(mixed $reason, string $field): string
    {
        if (! is_string($reason)) {
            throw new InvalidArgumentException(sprintf('%s is required.', $field));
        }

        $resolved = trim($reason);

        if ($resolved === '') {
            throw new InvalidArgumentException(sprintf('%s is required.', $field));
        }

        if (mb_strlen($resolved) > 2000) {
            throw new InvalidArgumentException(sprintf('%s must be at most 2000 characters.', $field));
        }

        return $resolved;
    }

    private function invalidateArtifactTruthCache(TenantReview $review): void
    {
        $this->derivedStateStore->invalidateModel(DerivedStateFamily::ArtifactTruth, $review, 'tenant_review');

        $review->loadMissing('currentExportReviewPack');

        $pack = $review->currentExportReviewPack;

        if ($pack instanceof ReviewPack) {
            $this->derivedStateStore->invalidateModel(DerivedStateFamily::ArtifactTruth, $pack, 'review_pack');
        }
    }
}
