<?php

namespace App\Services\Notification;

use App\Jobs\ProcessNotificationDelivery;
use App\Models\Customer;
use App\Models\NotificationBroadcastRecipient;
use App\Models\NotificationChannel;
use App\Models\NotificationDelivery;
use App\Models\NotificationEvent;
use App\Models\NotificationEventOccurrence;
use App\Models\NotificationMessage;
use App\Models\NotificationPreference;
use App\Models\NotificationTemplate;
use App\Models\User;
use App\Services\Notification\Adapters\EmailAdapter;
use App\Services\Notification\Adapters\SystemAdapter;
use App\Services\Notification\Adapters\TelegramAdapter;
use App\Services\Notification\Adapters\WhatsAppOfficialAdapter;
use App\Services\Notification\Adapters\WhatsAppUnofficialAdapter;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class NotificationManager
{
    public function emit(
        string $eventCode,
        Model $recipient,
        Model $subject,
        array $payload,
        string $occurrenceKey,
        array $context = [],
    ): ?NotificationMessage {
        $event = NotificationEvent::where('code', $eventCode)->where('enabled', true)->first();
        $tenantId = (int) ($recipient->tenant_id ?? $subject->tenant_id);
        if (! $event || ! $tenantId) {
            return null;
        }

        return DB::transaction(function () use ($event, $recipient, $subject, $payload, $occurrenceKey, $context, $tenantId) {
            $recipientOccurrenceKey = Str::limit(
                $occurrenceKey.'|'.$recipient->getMorphClass().'|'.$recipient->getKey(),
                255,
                ''
            );
            $occurrence = NotificationEventOccurrence::firstOrCreate([
                'tenant_id' => $tenantId,
                'notification_event_id' => $event->id,
                'subjectable_type' => $subject->getMorphClass(),
                'subjectable_id' => $subject->getKey(),
                'occurrence_key' => $recipientOccurrenceKey,
            ]);
            if (! $occurrence->wasRecentlyCreated) {
                return null;
            }

            $preferences = NotificationPreference::with('channelConfig')
                ->where('tenant_id', $tenantId)
                ->where('notification_event_id', $event->id)
                ->where('enabled', true)
                ->orderBy('priority')
                ->get();
            $systemConfigured = NotificationPreference::where('tenant_id', $tenantId)
                ->where('notification_event_id', $event->id)
                ->where('channel', 'system')
                ->exists();
            if (! $systemConfigured && in_array('system', $event->available_channels ?? [], true)) {
                $preferences->prepend(new NotificationPreference([
                    'tenant_id' => $tenantId,
                    'notification_event_id' => $event->id,
                    'channel' => 'system',
                    'enabled' => true,
                    'priority' => 1,
                    'send_delay_minutes' => 0,
                ]));
            }
            if ($preferences->isEmpty()) {
                $occurrence->update(['processed_at' => now()]);

                return null;
            }

            $message = NotificationMessage::create([
                'uuid' => Str::uuid(),
                'tenant_id' => $tenantId,
                'notification_event_id' => $event->id,
                'recipient_type' => $recipient->getMorphClass(),
                'recipient_id' => $recipient->getKey(),
                'subjectable_type' => $subject->getMorphClass(),
                'subjectable_id' => $subject->getKey(),
                'payload' => $payload,
                'context' => $context,
                'priority' => 5,
                'status' => 'queued',
            ]);

            foreach ($preferences as $preference) {
                $template = $this->template($tenantId, $event->id, $preference->channel);
                $channelConfig = $preference->channelConfig
                    ?: NotificationChannel::where('tenant_id', $tenantId)->where('channel', $preference->channel)->where('enabled', true)->where('is_default', true)->first();
                $destination = $this->destination($recipient, $preference->channel);
                if (! $template || ($preference->channel !== 'system' && ! $channelConfig) || ! $destination) {
                    NotificationDelivery::create([
                        'uuid' => Str::uuid(),
                        'notification_message_id' => $message->id,
                        'notification_channel_id' => $channelConfig?->id,
                        'channel' => $preference->channel,
                        'destination' => $destination,
                        'rendered_subject' => $template?->subject,
                        'rendered_body' => $template?->body ?? '',
                        'status' => 'skipped',
                        'error_message' => 'Template, channel, atau tujuan penerima belum tersedia.',
                    ]);

                    continue;
                }

                $delivery = NotificationDelivery::create([
                    'uuid' => Str::uuid(),
                    'notification_message_id' => $message->id,
                    'notification_channel_id' => $channelConfig?->id,
                    'channel' => $preference->channel,
                    'destination' => $destination,
                    'rendered_subject' => $this->render($template->subject, $payload),
                    'rendered_body' => $this->render($template->body, $payload),
                    'status' => 'queued',
                    'queued_at' => now(),
                    'provider_response' => [
                        'request_context' => [
                            'provider_template_name' => $template->provider_template_name,
                            'language' => $template->language,
                            'template_parameters' => collect($template->variables ?? [])->map(fn ($key) => data_get($payload, $key))->values()->all(),
                        ],
                    ],
                ]);
                $scheduledAt = $this->scheduledAt($preference);
                $message->update([
                    'notification_template_id' => $message->notification_template_id ?: $template->id,
                    'scheduled_at' => $scheduledAt,
                ]);
                ProcessNotificationDelivery::dispatch($delivery->id)
                    ->delay($scheduledAt)
                    ->onQueue('notifications-'.$this->queueName($preference->channel))
                    ->afterCommit();
            }
            $occurrence->update(['processed_at' => now()]);

            return $message->load('deliveries');
        });
    }

    public function deliver(NotificationDelivery $delivery): void
    {
        $delivery->load(['message.recipient', 'channelConfig']);
        $delivery->update(['status' => 'processing', 'attempt_count' => $delivery->attempt_count + 1]);
        try {
            $result = $this->adapter($delivery->channel)->send($delivery);
            $delivery->update([
                'status' => $delivery->channel === 'system' ? 'delivered' : 'sent',
                'provider_message_id' => $result['provider_message_id'] ?? null,
                'provider_response' => $result['response'] ?? null,
                'sent_at' => now(),
                'delivered_at' => $delivery->channel === 'system' ? now() : null,
                'error_code' => null,
                'error_message' => null,
            ]);
        } catch (\Throwable $exception) {
            $delivery->update([
                'status' => 'failed',
                'failed_at' => now(),
                'next_retry_at' => now()->addMinutes(min(60, 2 ** $delivery->attempt_count)),
                'error_code' => (string) $exception->getCode(),
                'error_message' => Str::limit($exception->getMessage(), 2000),
            ]);
            throw $exception;
        } finally {
            $this->refreshMessageStatus($delivery->message);
            $this->refreshBroadcast($delivery->message);
        }
    }

    public function render(?string $template, array $payload): ?string
    {
        if ($template === null) {
            return null;
        }

        return preg_replace_callback(
            '/{{\s*([a-zA-Z0-9_.]+)\s*}}/',
            function ($matches) use ($payload) {
                $value = data_get($payload, $matches[1]);
                if ($value === null && $matches[1] === 'tenant.name') {
                    $value = data_get($payload, 'tenant.tenant_name');
                }

                return (string) ($value ?? '');
            },
            $template,
        );
    }

    public function destination(Model $recipient, string $channel, bool $broadcast = false): ?string
    {
        if ($channel === 'system') {
            return (string) ($recipient instanceof User ? $recipient->id : $recipient->user_id);
        }
        if (! $recipient instanceof Customer) {
            return $channel === 'email' ? $recipient->email : null;
        }
        $type = match ($channel) {
            'whatsapp_official', 'whatsapp_unofficial' => 'whatsapp',
            'email' => 'email',
            'telegram' => 'telegram',
        };
        $contact = $recipient->contacts()
            ->where('type', $type)
            ->whereNull('opted_out_at')
            ->when($broadcast, fn ($q) => $q->where('can_receive_broadcast', true))
            ->orderByDesc('is_primary')
            ->first();
        if ($contact) {
            return $contact->value;
        }

        return match ($channel) {
            'whatsapp_official', 'whatsapp_unofficial' => $broadcast ? null : $recipient->whatsapp_number,
            'email' => $broadcast ? null : $recipient->email,
            default => null,
        };
    }

    private function template(int $tenantId, int $eventId, string $channel): ?NotificationTemplate
    {
        return NotificationTemplate::where('notification_event_id', $eventId)
            ->where('channel', $channel)
            ->where('enabled', true)
            ->when($channel === 'whatsapp_official', fn ($q) => $q->where('approval_status', 'approved')->whereNotNull('provider_template_name'))
            ->where(fn ($q) => $q->where('tenant_id', $tenantId)->orWhereNull('tenant_id'))
            ->orderByRaw('tenant_id IS NULL')
            ->orderByDesc('version')
            ->first();
    }

    private function adapter(string $channel)
    {
        return match ($channel) {
            'system' => app(SystemAdapter::class),
            'whatsapp_official' => app(WhatsAppOfficialAdapter::class),
            'whatsapp_unofficial' => app(WhatsAppUnofficialAdapter::class),
            'email' => app(EmailAdapter::class),
            'telegram' => app(TelegramAdapter::class),
        };
    }

    private function queueName(string $channel): string
    {
        return str_starts_with($channel, 'whatsapp') ? 'whatsapp' : $channel;
    }

    private function scheduledAt(NotificationPreference $preference): Carbon
    {
        $scheduled = now()->addMinutes($preference->send_delay_minutes);
        if (! $preference->quiet_hours_start || ! $preference->quiet_hours_end) {
            return $scheduled;
        }
        $start = $scheduled->copy()->setTimeFromTimeString($preference->quiet_hours_start);
        $end = $scheduled->copy()->setTimeFromTimeString($preference->quiet_hours_end);
        if ($end->lte($start)) {
            $end->addDay();
            if ($scheduled->lt($start)) {
                $start->subDay();
            }
        }

        return $scheduled->between($start, $end) ? $end : $scheduled;
    }

    private function refreshMessageStatus(NotificationMessage $message): void
    {
        $statuses = $message->deliveries()->pluck('status');
        $status = $statuses->contains(fn ($value) => in_array($value, ['queued', 'processing', 'pending'], true))
            ? 'processing'
            : ($statuses->contains(fn ($value) => in_array($value, ['sent', 'delivered', 'read'], true)) ? 'sent' : 'failed');
        $message->update(['status' => $status]);
    }

    private function refreshBroadcast(NotificationMessage $message): void
    {
        if (! $message->notification_broadcast_id) {
            return;
        }
        $recipient = NotificationBroadcastRecipient::where('notification_message_id', $message->id)->first();
        $messageStatus = $message->fresh()->status;
        if ($recipient) {
            $recipient->update([
                'status' => $messageStatus === 'sent' ? 'sent' : ($messageStatus === 'failed' ? 'failed' : 'queued'),
            ]);
        }
        $broadcast = $message->broadcast;
        if (! $broadcast) {
            return;
        }
        $pending = $broadcast->recipients()->whereIn('status', ['pending', 'queued'])->count();
        $broadcast->update([
            'processed_recipients' => $broadcast->recipients()->count(),
            'success_count' => $broadcast->recipients()->where('status', 'sent')->count(),
            'failed_count' => $broadcast->recipients()->whereIn('status', ['failed', 'skipped'])->count(),
            'status' => $pending === 0 ? 'completed' : 'processing',
        ]);
    }
}
