<?php

namespace App\Services\TenantAnnouncements;

use App\Enums\TenantAnnouncementAudience;
use App\Enums\TenantAnnouncementDeliveryStatus;
use App\Enums\TenantAnnouncementSegment;
use App\Enums\TenantAnnouncementStatus;
use App\Models\Tenant;
use App\Models\TenantAnnouncement;
use App\Models\TenantAnnouncementDelivery;
use App\Notifications\TenantAnnouncementNotification;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Notification;
use Throwable;

class TenantAnnouncementService
{
    /**
     * @return array{activated: int, archived: int, queued: int}
     */
    public function process(): array
    {
        $activated = $this->activateScheduled();
        $archived = $this->archiveExpired();
        $queued = $this->dispatchActiveEmails();

        return [
            'activated' => $activated,
            'archived' => $archived,
            'queued' => $queued,
        ];
    }

    public function visibleAnnouncementsForTenant(Tenant $tenant): Collection
    {
        $baseQuery = TenantAnnouncement::query()->active();

        $all = (clone $baseQuery)
            ->where('audience', TenantAnnouncementAudience::ALL->value)
            ->get();

        $tenants = (clone $baseQuery)
            ->where('audience', TenantAnnouncementAudience::TENANTS->value)
            ->whereHas('tenants', fn (Builder $query) => $query->where('tenant_id', $tenant->id))
            ->get();

        $segments = (clone $baseQuery)
            ->where('audience', TenantAnnouncementAudience::SEGMENTS->value)
            ->get()
            ->filter(function (TenantAnnouncement $announcement) use ($tenant): bool {
                $segments = $this->normalizeSegments($announcement->segments ?? []);

                return $announcement->matchesSegments($tenant, $segments);
            });

        return $all
            ->merge($tenants)
            ->merge($segments)
            ->sortByDesc(fn (TenantAnnouncement $announcement) => $announcement->starts_at ?? $announcement->created_at)
            ->values();
    }

    private function activateScheduled(): int
    {
        return TenantAnnouncement::query()
            ->where('status', TenantAnnouncementStatus::SCHEDULED->value)
            ->where(function (Builder $builder): void {
                $builder->whereNull('starts_at')
                    ->orWhere('starts_at', '<=', Carbon::now());
            })
            ->update([
                'status' => TenantAnnouncementStatus::ACTIVE->value,
                'updated_at' => now(),
            ]);
    }

    private function archiveExpired(): int
    {
        return TenantAnnouncement::query()
            ->where('status', TenantAnnouncementStatus::ACTIVE->value)
            ->whereNotNull('ends_at')
            ->where('ends_at', '<', Carbon::now())
            ->update([
                'status' => TenantAnnouncementStatus::ARCHIVED->value,
                'updated_at' => now(),
            ]);
    }

    private function dispatchActiveEmails(): int
    {
        $announcements = TenantAnnouncement::query()
            ->active()
            ->where('email_enabled', true)
            ->get();

        $queued = 0;

        foreach ($announcements as $announcement) {
            $queued += $this->dispatchEmailsForAnnouncement($announcement);
        }

        return $queued;
    }

    private function dispatchEmailsForAnnouncement(TenantAnnouncement $announcement): int
    {
        $tenants = $this->targetTenants($announcement);

        if ($tenants->isEmpty()) {
            return 0;
        }

        $deliveredTenantIds = TenantAnnouncementDelivery::query()
            ->where('tenant_announcement_id', $announcement->id)
            ->where('channel', 'mail')
            ->pluck('tenant_id')
            ->all();

        $queued = 0;

        foreach ($tenants as $tenant) {
            if (in_array($tenant->id, $deliveredTenantIds, true)) {
                continue;
            }

            $queued += $this->queueDelivery($announcement, $tenant);
        }

        return $queued;
    }

    private function queueDelivery(TenantAnnouncement $announcement, Tenant $tenant): int
    {
        $recipient = $this->resolveRecipient($tenant);

        if (! $recipient) {
            TenantAnnouncementDelivery::query()->create([
                'tenant_announcement_id' => $announcement->id,
                'tenant_id' => $tenant->id,
                'channel' => 'mail',
                'status' => TenantAnnouncementDeliveryStatus::SKIPPED->value,
                'failed_at' => now(),
                'failure_reason' => 'missing_recipient',
            ]);

            return 0;
        }

        try {
            Notification::route('mail', $recipient)
                ->notify(new TenantAnnouncementNotification($announcement, $tenant));

            TenantAnnouncementDelivery::query()->create([
                'tenant_announcement_id' => $announcement->id,
                'tenant_id' => $tenant->id,
                'channel' => 'mail',
                'status' => TenantAnnouncementDeliveryStatus::QUEUED->value,
                'sent_at' => now(),
            ]);

            return 1;
        } catch (Throwable $exception) {
            TenantAnnouncementDelivery::query()->create([
                'tenant_announcement_id' => $announcement->id,
                'tenant_id' => $tenant->id,
                'channel' => 'mail',
                'status' => TenantAnnouncementDeliveryStatus::FAILED->value,
                'failed_at' => now(),
                'failure_reason' => class_basename($exception),
            ]);
        }

        return 0;
    }

    private function resolveRecipient(Tenant $tenant): ?string
    {
        $email = $tenant->contact_email ?: $tenant->user?->email;

        return $email ?: null;
    }

    private function targetTenants(TenantAnnouncement $announcement): Collection
    {
        $query = Tenant::query()->with('user');

        if ($announcement->audience === TenantAnnouncementAudience::TENANTS) {
            $query->whereHas('announcements', fn (Builder $builder) => $builder->where('tenant_announcements.id', $announcement->id));
        }

        if ($announcement->audience === TenantAnnouncementAudience::SEGMENTS) {
            $this->applySegmentFilters($query, $this->normalizeSegments($announcement->segments ?? []));
        }

        return $query->get();
    }

    /**
     * @param  array<int, TenantAnnouncementSegment|string>  $segments
     */
    private function applySegmentFilters(Builder $query, array $segments): void
    {
        foreach ($segments as $segment) {
            $normalized = $segment instanceof TenantAnnouncementSegment
                ? $segment
                : TenantAnnouncementSegment::tryFrom((string) $segment);

            if (! $normalized) {
                continue;
            }

            match ($normalized) {
                TenantAnnouncementSegment::ACTIVE_PACKAGE => $query->whereHas('activeResellerPackage'),
                TenantAnnouncementSegment::ACTIVE_STATUS => $query
                    ->where('is_active', true)
                    ->where('is_suspended', false)
                    ->whereNull('pending_deletion_at')
                    ->whereNull('anonymized_at'),
            };
        }
    }

    /**
     * @param  array<int, mixed>  $segments
     * @return array<int, TenantAnnouncementSegment|string>
     */
    private function normalizeSegments(array $segments): array
    {
        return array_values(array_filter($segments, fn ($segment) => $segment !== null && $segment !== ''));
    }
}
