<?php

namespace App\Services\Packages;

use App\Models\Event;
use App\Models\EventPackage;
use App\Models\Tenant;
use App\Services\Tenant\TenantUsageService;

class PackageLimitEvaluator
{
    public function __construct(private readonly TenantUsageService $tenantUsageService) {}

    public function assessEventCreation(Tenant $tenant, ?string $includedPackageSlug = null): ?array
    {
        $hasEndcustomerPackage = $tenant->tenantPackages()
            ->where('active', true)
            ->whereHas('package', fn ($query) => $query->withTrashed()->where('type', 'endcustomer'))
            ->exists();

        if ($hasEndcustomerPackage) {
            return null;
        }

        if ($tenant->hasEventAllowanceFor($includedPackageSlug)) {
            return null;
        }

        $package = $tenant->getActiveResellerPackageFor($includedPackageSlug);

        if (! $package) {
            if ($includedPackageSlug) {
                $hasAnyActive = $tenant->tenantPackages()
                    ->where('active', true)
                    ->where(function ($query) {
                        $query->whereNull('expires_at')->orWhere('expires_at', '>', now());
                    })
                    ->whereHas('package', fn ($query) => $query->withTrashed()->where('type', 'reseller'))
                    ->exists();

                if ($hasAnyActive) {
                    return [
                        'code' => 'event_tier_unavailable',
                        'title' => __('api.packages.event_tier_unavailable.title'),
                        'message' => __('api.packages.event_tier_unavailable.message'),
                        'status' => 402,
                        'meta' => [
                            'scope' => 'events',
                            'requested_tier' => $includedPackageSlug,
                        ],
                    ];
                }
            }

            $latestResellerPackage = $tenant->tenantPackages()
                ->with('package')
                ->whereHas('package', fn ($query) => $query->withTrashed()->where('type', 'reseller'))
                ->orderByDesc('purchased_at')
                ->orderByDesc('id')
                ->first();

            if ($latestResellerPackage && $latestResellerPackage->package) {
                $limit = $latestResellerPackage->package->max_events_per_year ?? 0;

                return [
                    'code' => 'event_limit_exceeded',
                    'title' => __('api.packages.event_limit_exceeded.title'),
                    'message' => __('api.packages.event_limit_exceeded.message'),
                    'status' => 402,
                    'meta' => [
                        'scope' => 'events',
                        'used' => (int) $latestResellerPackage->used_events,
                        'limit' => $limit,
                        'remaining' => max(0, $limit - $latestResellerPackage->used_events),
                        'tenant_package_id' => $latestResellerPackage->id,
                        'package_id' => $latestResellerPackage->package_id,
                    ],
                ];
            }

            return [
                'code' => 'event_limit_missing',
                'title' => __('api.packages.event_limit_missing.title'),
                'message' => __('api.packages.event_limit_missing.message'),
                'status' => 402,
                'meta' => [
                    'scope' => 'events',
                    'used' => 0,
                    'limit' => 0,
                    'remaining' => 0,
                    'tenant_package_id' => null,
                    'package_id' => null,
                ],
            ];
        }

        $limit = $package->package->max_events_per_year ?? 0;

        return [
            'code' => 'event_limit_exceeded',
            'title' => __('api.packages.event_limit_exceeded.title'),
            'message' => __('api.packages.event_limit_exceeded.message'),
            'status' => 402,
            'meta' => [
                'scope' => 'events',
                'used' => (int) $package->used_events,
                'limit' => $limit,
                'remaining' => max(0, $limit - $package->used_events),
                'tenant_package_id' => $package->id,
                'package_id' => $package->package_id,
            ],
        ];
    }

    public function assessPhotoUpload(
        Tenant $tenant,
        int $eventId,
        ?Event $preloadedEvent = null,
        ?int $incomingBytes = null
    ): ?array {
        [$event, $eventPackage] = $this->resolveEventAndPackage($tenant, $eventId, $preloadedEvent);

        if (! $event) {
            return [
                'code' => 'event_not_found',
                'title' => __('api.packages.event_not_found.title'),
                'message' => __('api.packages.event_not_found.message'),
                'status' => 404,
                'meta' => [
                    'scope' => 'photos',
                    'event_id' => $eventId,
                ],
            ];
        }

        if (! $eventPackage || ! $eventPackage->package) {
            return [
                'code' => 'event_package_missing',
                'title' => __('api.packages.event_package_missing.title'),
                'message' => __('api.packages.event_package_missing.message'),
                'status' => 409,
                'meta' => [
                    'scope' => 'photos',
                    'event_id' => $event->id,
                ],
            ];
        }

        $maxPhotos = $eventPackage->effectivePhotoLimit();

        if ($maxPhotos !== null && $eventPackage->used_photos >= $maxPhotos) {
            return [
                'code' => 'photo_limit_exceeded',
                'title' => __('api.packages.photo_limit_exceeded.title'),
                'message' => __('api.packages.photo_limit_exceeded.message'),
                'status' => 402,
                'meta' => [
                    'scope' => 'photos',
                    'used' => (int) $eventPackage->used_photos,
                    'limit' => (int) $maxPhotos,
                    'remaining' => 0,
                    'event_id' => $event->id,
                    'package_id' => $eventPackage->package_id,
                ],
            ];
        }

        $tenantPhotoLimit = $this->normalizeTenantLimit($tenant->max_photos_per_event);

        if ($tenantPhotoLimit !== null && ($maxPhotos === null || $tenantPhotoLimit < $maxPhotos)) {
            if ($eventPackage->used_photos >= $tenantPhotoLimit) {
                return [
                    'code' => 'tenant_photo_limit_exceeded',
                    'title' => __('api.packages.tenant_photo_limit_exceeded.title'),
                    'message' => __('api.packages.tenant_photo_limit_exceeded.message'),
                    'status' => 402,
                    'meta' => [
                        'scope' => 'photos',
                        'used' => (int) $eventPackage->used_photos,
                        'limit' => (int) $tenantPhotoLimit,
                        'remaining' => max(0, (int) $tenantPhotoLimit - (int) $eventPackage->used_photos),
                        'event_id' => $event->id,
                        'limit_source' => 'tenant',
                    ],
                ];
            }
        }

        $storageLimitBytes = $this->tenantUsageService->storageLimitBytes($tenant);

        if ($storageLimitBytes !== null) {
            $usedBytes = $this->tenantUsageService->storageUsedBytes($tenant);
            $projectedBytes = $usedBytes + max(0, (int) ($incomingBytes ?? 0));

            if ($projectedBytes >= $storageLimitBytes) {
                return [
                    'code' => 'tenant_storage_limit_exceeded',
                    'title' => __('api.packages.tenant_storage_limit_exceeded.title'),
                    'message' => __('api.packages.tenant_storage_limit_exceeded.message'),
                    'status' => 402,
                    'meta' => [
                        'scope' => 'storage',
                        'used_bytes' => $usedBytes,
                        'limit_bytes' => $storageLimitBytes,
                        'remaining_bytes' => max(0, $storageLimitBytes - $usedBytes),
                        'event_id' => $event->id,
                        'limit_source' => 'tenant',
                    ],
                ];
            }
        }

        return null;
    }

    public function resolveEventPackageForPhotoUpload(
        Tenant $tenant,
        int $eventId,
        ?Event $preloadedEvent = null
    ): ?EventPackage {
        [, $eventPackage] = $this->resolveEventAndPackage($tenant, $eventId, $preloadedEvent);

        return $eventPackage;
    }

    public function summarizeEventPackage(EventPackage $eventPackage): array
    {
        $limits = $eventPackage->effectiveLimits();

        $photoSummary = $this->buildUsageSummary(
            (int) $eventPackage->used_photos,
            $limits['max_photos'],
            config('package-limits.photo_thresholds', [])
        );

        $guestSummary = $this->buildUsageSummary(
            (int) $eventPackage->used_guests,
            $limits['max_guests'],
            config('package-limits.guest_thresholds', [])
        );

        $gallerySummary = $this->buildGallerySummary(
            $eventPackage,
            config('package-limits.gallery_warning_days', [])
        );

        return [
            'photos' => $photoSummary,
            'guests' => $guestSummary,
            'gallery' => $gallerySummary,
            'can_upload_photos' => $photoSummary['state'] !== 'limit_reached' && $gallerySummary['state'] !== 'expired',
            'can_add_guests' => $guestSummary['state'] !== 'limit_reached',
        ];
    }

    /**
     * @return array{0: ?Event, 1: ?\App\Models\EventPackage}
     */
    private function resolveEventAndPackage(
        Tenant $tenant,
        int $eventId,
        ?Event $preloadedEvent = null
    ): array {
        $event = $preloadedEvent;

        if (! $event) {
            $event = Event::with(['eventPackage.package', 'eventPackages.package'])
                ->find($eventId);
        }

        if (! $event || $event->tenant_id !== $tenant->id) {
            return [null, null];
        }

        $eventPackage = $event->eventPackage;

        if (! $eventPackage && method_exists($event, 'eventPackages')) {
            $eventPackage = $event->eventPackages()
                ->with('package')
                ->orderByDesc('purchased_at')
                ->orderByDesc('created_at')
                ->first();
        }

        if ($eventPackage && ! $eventPackage->relationLoaded('package')) {
            $eventPackage->load('package');
        }

        return [$event, $eventPackage];
    }

    /**
     * @param  array<int|float|string>  $rawThresholds
     */
    private function buildUsageSummary(int $used, ?int $limit, array $rawThresholds): array
    {
        $thresholds = collect($rawThresholds)
            ->filter(fn ($value) => is_numeric($value) && $value > 0 && $value < 1)
            ->map(fn ($value) => round((float) $value, 4))
            ->unique()
            ->sort()
            ->values()
            ->all();

        if ($limit === null || $limit <= 0) {
            return [
                'limit' => null,
                'used' => $used,
                'remaining' => null,
                'percentage' => null,
                'state' => 'unlimited',
                'threshold_reached' => null,
                'next_threshold' => $thresholds[0] ?? null,
                'thresholds' => $thresholds,
            ];
        }

        $clampedLimit = max(1, (int) $limit);
        $ratio = $used / $clampedLimit;
        $percentage = round(min(1, $ratio), 4);
        $remaining = max(0, $clampedLimit - $used);
        $state = 'ok';
        $thresholdReached = null;
        $nextThreshold = null;

        foreach ($thresholds as $threshold) {
            if ($percentage >= $threshold) {
                $thresholdReached = $threshold;
                if ($state !== 'limit_reached') {
                    $state = 'warning';
                }
            } elseif ($nextThreshold === null) {
                $nextThreshold = $threshold;
            }
        }

        if ($used >= $clampedLimit) {
            $state = 'limit_reached';
            $thresholdReached = 1.0;
            $nextThreshold = null;
        }

        return [
            'limit' => $clampedLimit,
            'used' => $used,
            'remaining' => $remaining,
            'percentage' => $percentage,
            'state' => $state,
            'threshold_reached' => $thresholdReached,
            'next_threshold' => $nextThreshold,
            'thresholds' => $thresholds,
        ];
    }

    /**
     * @param  array<int|string>  $warningDays
     */
    private function buildGallerySummary(EventPackage $eventPackage, array $warningDays): array
    {
        $expiresAt = $eventPackage->gallery_expires_at;
        $warningValues = collect($warningDays)
            ->filter(fn ($value) => is_numeric($value) && $value >= 0)
            ->map(fn ($value) => (int) $value)
            ->unique()
            ->sort()
            ->values()
            ->all();

        if (! $expiresAt) {
            return [
                'state' => 'unlimited',
                'expires_at' => null,
                'days_remaining' => null,
                'warning_thresholds' => $warningValues,
                'warning_triggered' => null,
                'warning_sent_at' => null,
                'expired_notified_at' => null,
            ];
        }

        $daysRemaining = now()->diffInDays($expiresAt, false);
        $state = 'ok';
        $warningTriggered = null;

        foreach ($warningValues as $threshold) {
            if ($daysRemaining <= $threshold && $daysRemaining >= 0) {
                $warningTriggered = $threshold;
                $state = 'warning';
                break;
            }
        }

        if ($daysRemaining < 0) {
            $state = 'expired';
        }

        return [
            'state' => $state,
            'expires_at' => $expiresAt->toIso8601String(),
            'days_remaining' => $daysRemaining,
            'warning_thresholds' => $warningValues,
            'warning_triggered' => $warningTriggered,
            'warning_sent_at' => $eventPackage->gallery_warning_sent_at?->toIso8601String(),
            'expired_notified_at' => $eventPackage->gallery_expired_notified_at?->toIso8601String(),
        ];
    }

    private function normalizeTenantLimit(?int $value): ?int
    {
        if ($value === null) {
            return null;
        }

        $value = (int) $value;

        if ($value <= 0) {
            return null;
        }

        return $value;
    }
}
