<?php

declare(strict_types=1);

namespace App\Support\Inventory;

use App\Models\InventoryItem;
use App\Models\OperationRun;
use App\Models\ManagedEnvironment;
use App\Services\Inventory\CoverageCapabilitiesResolver;
use Illuminate\Support\Collection;

final class TenantCoverageTruthResolver
{
    public function __construct(
        private readonly CoverageCapabilitiesResolver $coverageCapabilities,
    ) {}

    public function resolve(ManagedEnvironment|int $tenant): TenantCoverageTruth
    {
        $tenantId = $tenant instanceof ManagedEnvironment
            ? (int) $tenant->getKey()
            : (int) $tenant;

        $basisRun = OperationRun::latestCompletedCoverageBearingInventorySyncForTenant($tenantId);
        $basisCoverage = $basisRun?->inventoryCoverage();

        /** @var array<string, int> $countsByType */
        $countsByType = InventoryItem::query()
            ->where('managed_environment_id', $tenantId)
            ->selectRaw('policy_type, COUNT(*) as aggregate')
            ->groupBy('policy_type')
            ->pluck('aggregate', 'policy_type')
            ->map(static fn (mixed $value): int => (int) $value)
            ->all();

        $rows = $this->supportedTypes()
            ->map(function (array $meta) use ($basisCoverage, $countsByType): TenantCoverageTypeTruth {
                $type = (string) $meta['type'];
                $segment = (string) $meta['segment'];
                $basisRow = $basisCoverage?->row($type);
                $coverageState = is_string($basisRow['status'] ?? null)
                    ? (string) $basisRow['status']
                    : TenantCoverageTypeTruth::StateUnknown;
                $observedItemCount = (int) ($countsByType[$type] ?? 0);
                $basisItemCount = is_int($basisRow['item_count'] ?? null)
                    ? (int) $basisRow['item_count']
                    : null;
                $basisErrorCode = is_string($basisRow['error_code'] ?? null)
                    ? (string) $basisRow['error_code']
                    : null;
                $followUpRequired = $coverageState !== TenantCoverageTypeTruth::StateSucceeded;

                return new TenantCoverageTypeTruth(
                    key: sprintf('%s:%s', $segment, $type),
                    type: $type,
                    segment: $segment,
                    label: (string) ($meta['label'] ?? $type),
                    category: (string) ($meta['category'] ?? 'Other'),
                    platform: is_string($meta['platform'] ?? null) ? (string) $meta['platform'] : null,
                    coverageState: $coverageState,
                    followUpRequired: $followUpRequired,
                    followUpPriority: self::followUpPriorityForState($coverageState),
                    observedItemCount: $observedItemCount,
                    basisItemCount: $basisItemCount,
                    basisErrorCode: $basisErrorCode,
                    restoreMode: is_string($meta['restore'] ?? null) ? (string) $meta['restore'] : null,
                    riskLevel: is_string($meta['risk'] ?? null) ? (string) $meta['risk'] : null,
                    supportsDependencies: $segment === 'policy' && $this->coverageCapabilities->supportsDependencies($type),
                    followUpGuidance: self::followUpGuidanceForState($coverageState, $basisErrorCode),
                    isBasisPayloadBacked: $basisRow !== null,
                );
            })
            ->sort(function (TenantCoverageTypeTruth $left, TenantCoverageTypeTruth $right): int {
                $priority = $left->followUpPriority <=> $right->followUpPriority;

                if ($priority !== 0) {
                    return $priority;
                }

                $observed = $right->observedItemCount <=> $left->observedItemCount;

                if ($observed !== 0) {
                    return $observed;
                }

                return strnatcasecmp($left->label, $right->label);
            })
            ->values()
            ->all();

        return new TenantCoverageTruth(
            tenantId: $tenantId,
            basisRun: $basisRun,
            hasCurrentCoverageResult: $basisCoverage instanceof InventoryCoverage,
            supportedTypeCount: count($rows),
            succeededTypeCount: $this->countRowsByState($rows, TenantCoverageTypeTruth::StateSucceeded),
            failedTypeCount: $this->countRowsByState($rows, TenantCoverageTypeTruth::StateFailed),
            skippedTypeCount: $this->countRowsByState($rows, TenantCoverageTypeTruth::StateSkipped),
            unknownTypeCount: $this->countRowsByState($rows, TenantCoverageTypeTruth::StateUnknown),
            followUpTypeCount: count(array_filter(
                $rows,
                static fn (TenantCoverageTypeTruth $row): bool => $row->followUpRequired,
            )),
            observedItemTotal: array_sum($countsByType),
            rows: $rows,
        );
    }

    /**
     * @return Collection<int, array{type: string, label: string, category: string, platform?: string|null, restore?: string|null, risk?: string|null, segment: 'policy'|'foundation'}>
     */
    private function supportedTypes(): Collection
    {
        $supported = collect(InventoryPolicyTypeMeta::supported())
            ->filter(static fn (array $row): bool => is_string($row['type'] ?? null) && $row['type'] !== '')
            ->map(static fn (array $row): array => array_merge($row, ['segment' => 'policy']));

        $foundations = collect(InventoryPolicyTypeMeta::foundations())
            ->filter(static fn (array $row): bool => is_string($row['type'] ?? null) && $row['type'] !== '')
            ->map(static fn (array $row): array => array_merge($row, ['segment' => 'foundation']));

        return $supported
            ->merge($foundations)
            ->values();
    }

    public static function followUpPriorityForState(string $coverageState): int
    {
        return match ($coverageState) {
            TenantCoverageTypeTruth::StateFailed => 0,
            TenantCoverageTypeTruth::StateUnknown => 1,
            TenantCoverageTypeTruth::StateSkipped => 2,
            default => 3,
        };
    }

    public static function followUpGuidanceForState(string $coverageState, ?string $basisErrorCode): string
    {
        return match (true) {
            $coverageState === TenantCoverageTypeTruth::StateFailed && in_array($basisErrorCode, [
                'graph_forbidden',
                'provider_consent_missing',
                'provider_permission_missing',
                'provider_permission_denied',
            ], true) => 'Review provider consent or permissions, then rerun inventory sync.',
            $coverageState === TenantCoverageTypeTruth::StateFailed && in_array($basisErrorCode, [
                'graph_throttled',
                'graph_transient',
                'rate_limited',
                'network_unreachable',
            ], true) => 'Retry inventory sync after the provider recovers.',
            $coverageState === TenantCoverageTypeTruth::StateFailed => 'Review the latest inventory sync details before retrying.',
            $coverageState === TenantCoverageTypeTruth::StateSkipped => 'Run inventory sync again with the required types selected.',
            $coverageState === TenantCoverageTypeTruth::StateUnknown => 'No current basis result exists for this type. Run inventory sync to confirm coverage.',
            default => 'No follow-up is currently required.',
        };
    }

    /**
     * @param  list<TenantCoverageTypeTruth>  $rows
     */
    private function countRowsByState(array $rows, string $state): int
    {
        return count(array_filter(
            $rows,
            static fn (TenantCoverageTypeTruth $row): bool => $row->coverageState === $state,
        ));
    }
}
