<?php

declare(strict_types=1);

namespace App\Services;

use App\Jobs\GenerateReviewPackJob;
use App\Models\Finding;
use App\Models\OperationRun;
use App\Models\ReviewPack;
use App\Models\StoredReport;
use App\Models\Tenant;
use App\Models\User;
use App\Support\OperationRunType;
use App\Support\ReviewPackStatus;
use Illuminate\Support\Facades\URL;

class ReviewPackService
{
    public function __construct(
        private OperationRunService $operationRunService,
    ) {}

    /**
     * Create an OperationRun + ReviewPack and dispatch the generation job.
     *
     * @param  array<string, mixed>  $options
     */
    public function generate(Tenant $tenant, User $user, array $options = []): ReviewPack
    {
        $options = $this->normalizeOptions($options);
        $fingerprint = $this->computeFingerprint($tenant, $options);

        $existing = $this->findExistingPack($tenant, $fingerprint);
        if ($existing instanceof ReviewPack) {
            return $existing;
        }

        $operationRun = $this->operationRunService->ensureRun(
            tenant: $tenant,
            type: OperationRunType::ReviewPackGenerate->value,
            inputs: [
                'include_pii' => $options['include_pii'],
                'include_operations' => $options['include_operations'],
            ],
            initiator: $user,
        );

        $reviewPack = ReviewPack::create([
            'tenant_id' => (int) $tenant->getKey(),
            'workspace_id' => (int) $tenant->workspace_id,
            'operation_run_id' => (int) $operationRun->getKey(),
            'initiated_by_user_id' => (int) $user->getKey(),
            'status' => ReviewPackStatus::Queued->value,
            'options' => $options,
            'summary' => [],
        ]);

        $this->operationRunService->dispatchOrFail($operationRun, function () use ($reviewPack, $operationRun): void {
            GenerateReviewPackJob::dispatch(
                reviewPackId: (int) $reviewPack->getKey(),
                operationRunId: (int) $operationRun->getKey(),
            );
        });

        return $reviewPack;
    }

    /**
     * Compute a deterministic fingerprint for deduplication.
     *
     * @param  array<string, mixed>  $options
     */
    public function computeFingerprint(Tenant $tenant, array $options): string
    {
        $reportFingerprints = StoredReport::query()
            ->where('tenant_id', (int) $tenant->getKey())
            ->whereIn('report_type', [
                StoredReport::REPORT_TYPE_PERMISSION_POSTURE,
                StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES,
            ])
            ->orderBy('report_type')
            ->pluck('fingerprint')
            ->toArray();

        $maxFindingDate = Finding::query()
            ->where('tenant_id', (int) $tenant->getKey())
            ->whereIn('status', Finding::openStatusesForQuery())
            ->max('updated_at');

        $data = [
            'tenant_id' => (int) $tenant->getKey(),
            'include_pii' => (bool) ($options['include_pii'] ?? true),
            'include_operations' => (bool) ($options['include_operations'] ?? true),
            'report_fingerprints' => $reportFingerprints,
            'max_finding_date' => $maxFindingDate,
            'rbac_last_checked_at' => $tenant->rbac_last_checked_at?->toIso8601String(),
        ];

        return hash('sha256', json_encode($data, JSON_THROW_ON_ERROR));
    }

    /**
     * Generate a signed download URL for a review pack.
     */
    public function generateDownloadUrl(ReviewPack $pack): string
    {
        $ttlMinutes = (int) config('tenantpilot.review_pack.download_url_ttl_minutes', 60);

        return URL::signedRoute(
            'admin.review-packs.download',
            ['reviewPack' => $pack->getKey()],
            now()->addMinutes($ttlMinutes),
        );
    }

    /**
     * Find an existing ready, non-expired pack with the same fingerprint.
     */
    public function findExistingPack(Tenant $tenant, string $fingerprint): ?ReviewPack
    {
        return ReviewPack::query()
            ->forTenant((int) $tenant->getKey())
            ->ready()
            ->where('fingerprint', $fingerprint)
            ->where('expires_at', '>', now())
            ->first();
    }

    /**
     * Check if a generation run is currently active for this tenant.
     */
    public function checkActiveRun(Tenant $tenant): bool
    {
        return OperationRun::query()
            ->where('tenant_id', (int) $tenant->getKey())
            ->where('type', OperationRunType::ReviewPackGenerate->value)
            ->active()
            ->exists();
    }

    /**
     * @param  array<string, mixed>  $options
     * @return array{include_pii: bool, include_operations: bool}
     */
    private function normalizeOptions(array $options): array
    {
        return [
            'include_pii' => (bool) ($options['include_pii'] ?? config('tenantpilot.review_pack.include_pii_default', true)),
            'include_operations' => (bool) ($options['include_operations'] ?? config('tenantpilot.review_pack.include_operations_default', true)),
        ];
    }
}
