<?php

namespace App\Services;

use App\Models\Event;
use App\Models\EventJoinToken;
use App\Models\GuestPolicySetting;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class EventJoinTokenService
{
    public function createToken(Event $event, array $attributes = []): EventJoinToken
    {
        return DB::transaction(function () use ($event, $attributes) {
            $tokenValue = $this->generateUniqueToken();

            $payload = [
                'event_id' => $event->id,
                'token' => $tokenValue,
                'label' => Arr::get($attributes, 'label'),
                'usage_limit' => Arr::get($attributes, 'usage_limit'),
                'metadata' => Arr::get($attributes, 'metadata', []),
            ];

            if ($expiresAt = Arr::get($attributes, 'expires_at')) {
                $payload['expires_at'] = $expiresAt instanceof Carbon
                    ? $expiresAt
                    : Carbon::parse($expiresAt);
            } else {
                $ttlHours = (int) (GuestPolicySetting::current()->join_token_ttl_hours ?? 0);

                if ($ttlHours > 0) {
                    $payload['expires_at'] = now()->addHours($ttlHours);
                }
            }

            if ($createdBy = Arr::get($attributes, 'created_by')) {
                $payload['created_by'] = $createdBy;
            }

            return tap(EventJoinToken::create($payload), function (EventJoinToken $model) use ($tokenValue) {
                $model->setAttribute('plain_token', $tokenValue);
            });
        });
    }

    public function revoke(EventJoinToken $joinToken, ?string $reason = null): EventJoinToken
    {
        unset($joinToken->plain_token);
        $joinToken->revoked_at = now();

        if ($reason) {
            $metadata = $joinToken->metadata ?? [];
            $metadata['revoked_reason'] = $reason;
            $joinToken->metadata = $metadata;
        }

        $joinToken->save();

        return $joinToken;
    }

    public function incrementUsage(EventJoinToken $joinToken): void
    {
        $joinToken->increment('usage_count');

        $event = $joinToken->event()
            ->with(['eventPackage.package', 'eventPackages.package', 'tenant'])
            ->first();

        if ($event && $event->tenant) {
            $usageTracker = app(\App\Services\Packages\PackageUsageTracker::class);
            $limitEvaluator = app(\App\Services\Packages\PackageLimitEvaluator::class);

            $eventPackage = $limitEvaluator->resolveEventPackageForPhotoUpload($event->tenant, $event->id, $event);

            if ($eventPackage && $eventPackage->package?->max_guests !== null) {
                $previous = (int) $eventPackage->used_guests;
                $eventPackage->increment('used_guests');
                $eventPackage->refresh();

                $usageTracker->recordGuestUsage($eventPackage, $previous, 1);
            }
        }
    }

    public function findToken(string $token, bool $includeInactive = false): ?EventJoinToken
    {
        $hash = $this->hashToken($token);

        return EventJoinToken::query()
            ->where(function ($query) use ($hash, $token) {
                $query->where('token_hash', $hash)
                    ->orWhere(function ($inner) use ($token) {
                        $inner->whereNull('token_hash')
                            ->where('token', $token);
                    });
            })
            ->when(! $includeInactive, function ($query) {
                $query->whereNull('revoked_at')
                    ->where(function ($query) {
                        $query->whereNull('expires_at')
                            ->orWhere('expires_at', '>', now());
                    })
                    ->where(function ($query) {
                        $query->whereNull('usage_limit')
                            ->orWhereColumn('usage_limit', '>', 'usage_count');
                    });
            })
            ->first();
    }

    public function findActiveToken(string $token): ?EventJoinToken
    {
        return $this->findToken($token);
    }

    protected function generateUniqueToken(int $length = 48): string
    {
        do {
            $token = Str::random($length);
            $hash = $this->hashToken($token);
        } while (EventJoinToken::where('token_hash', $hash)->exists());

        return $token;
    }

    protected function hashToken(string $token): string
    {
        return hash('sha256', $token);
    }
}
