<?php

namespace App\Domain\Notification\Services;

use App\Domain\Notification\Enums\NotificationChannel;
use App\Domain\Notification\Enums\NotificationPriority;
use App\Domain\Notification\Enums\NotificationStatus;
use App\Domain\Notification\Models\NotificationLog;
use App\Domain\Notification\Models\NotificationPreference;
use App\Domain\Notification\Models\NotificationTemplate;
use App\Domain\Shared\Exceptions\DomainException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;

class NotificationService
{
    /**
     * Send notification across resolved channels.
     *
     * @return array<int, array{channel: string, status: string, error?: string}>
     */
    public function send(
        string $eventType,
        string $recipientType,
        int $recipientId,
        int $academyId,
        array $variables = [],
        NotificationPriority $priority = NotificationPriority::Normal,
        ?string $recipientEmail = null,
        ?string $recipientPhone = null,
    ): array {
        $results = [];

        $channels = $this->resolveChannels($recipientType, $recipientId, $eventType, $priority);

        foreach ($channels as $channel) {
            $template = $this->resolveTemplate($academyId, $eventType, $channel);

            if (!$template) {
                Log::warning("No template found for {$eventType}/{$channel->value} in academy {$academyId}");
                continue;
            }

            $rendered = $this->renderTemplate($template, $variables);

            // Duplicate check for email (max 1 per recipient per event within 5 min)
            if ($channel === NotificationChannel::Email) {
                $exists = NotificationLog::where('event_type', $eventType)
                    ->where('recipient_type', $recipientType)
                    ->where('recipient_id', $recipientId)
                    ->where('channel', $channel)
                    ->where('created_at', '>=', now()->subMinutes(5))
                    ->exists();

                if ($exists) {
                    continue;
                }
            }

            try {
                match ($channel) {
                    NotificationChannel::InApp => $this->sendInApp(
                        $recipientType, $recipientId, $academyId, $eventType, $rendered
                    ),
                    NotificationChannel::Email => $this->sendEmail(
                        $recipientEmail, $rendered, $recipientType, $recipientId, $academyId, $eventType
                    ),
                    NotificationChannel::Sms => $this->sendSms(
                        $recipientPhone, $rendered, $recipientType, $recipientId, $academyId, $eventType
                    ),
                };

                $results[] = ['channel' => $channel->value, 'status' => 'sent'];
            } catch (\Throwable $e) {
                $this->logNotification(
                    $academyId, $recipientType, $recipientId, $eventType, $channel,
                    $rendered['subject'] ?? null, $rendered['body'],
                    NotificationStatus::Failed, $e->getMessage()
                );

                $results[] = ['channel' => $channel->value, 'status' => 'failed', 'error' => $e->getMessage()];
            }
        }

        return $results;
    }

    /**
     * Convenience: send notification by resolving academy from recipient.
     */
    public function sendSimple(
        string $type,
        int $recipientId,
        string $recipientType,
        array $data = [],
        string $priority = 'normal',
    ): array {
        $academyId = (int) app('current_academy')?->id;

        return $this->send(
            eventType: $type,
            recipientType: $recipientType,
            recipientId: $recipientId,
            academyId: $academyId,
            variables: $data,
            priority: NotificationPriority::from($priority),
        );
    }

    /**
     * Send notification to all users with a specific role in the current academy.
     */
    public function sendToRole(
        string $type,
        string $role,
        array $data = [],
        string $priority = 'normal',
    ): void {
        $academyId = (int) app('current_academy')?->id;

        $users = \App\Models\User::whereHas('roles', function ($q) use ($role) {
            $q->where('slug', $role);
        })->where('academy_id', $academyId)->get();

        foreach ($users as $user) {
            try {
                $this->send(
                    eventType: $type,
                    recipientType: 'user',
                    recipientId: $user->id,
                    academyId: $academyId,
                    variables: $data,
                    priority: NotificationPriority::from($priority),
                    recipientEmail: $user->email,
                );
            } catch (\Throwable $e) {
                Log::warning("sendToRole failed for user {$user->id}: " . $e->getMessage());
            }
        }
    }

    /**
     * Mark an in-app notification as read.
     */
    public function markAsRead(int $logId): void
    {
        NotificationLog::where('id', $logId)
            ->where('status', NotificationStatus::Sent)
            ->where('channel', NotificationChannel::InApp)
            ->update(['read_at' => now(), 'status' => NotificationStatus::Read->value]);
    }

    /**
     * Get unread in-app notification count.
     */
    public function getUnreadCount(string $recipientType, int $recipientId): int
    {
        return NotificationLog::where('recipient_type', $recipientType)
            ->where('recipient_id', $recipientId)
            ->where('channel', NotificationChannel::InApp)
            ->where('status', NotificationStatus::Sent)
            ->count();
    }

    /**
     * Get recent in-app notifications for a recipient.
     */
    public function getInAppNotifications(string $recipientType, int $recipientId, int $limit = 20): Collection
    {
        return NotificationLog::where('recipient_type', $recipientType)
            ->where('recipient_id', $recipientId)
            ->where('channel', NotificationChannel::InApp)
            ->orderByDesc('created_at')
            ->limit($limit)
            ->get();
    }

    /**
     * Resolve which channels to dispatch to based on preferences and priority.
     *
     * @return NotificationChannel[]
     */
    private function resolveChannels(
        string $recipientType,
        int $recipientId,
        string $eventType,
        NotificationPriority $priority
    ): array {
        // In-app is ALWAYS on
        $channels = [NotificationChannel::InApp];

        if ($recipientType === 'user') {
            $pref = NotificationPreference::where('user_id', $recipientId)
                ->where('event_type', $eventType)
                ->first();

            if ($pref) {
                if ($pref->channel_email) {
                    $channels[] = NotificationChannel::Email;
                }
                if ($pref->channel_sms) {
                    $channels[] = NotificationChannel::Sms;
                }

                // Digest mode: normal priority only gets in-app immediately
                if ($pref->digest_mode && $priority === NotificationPriority::Normal) {
                    return [NotificationChannel::InApp];
                }
            } else {
                // No preference record = all channels enabled (default)
                $channels[] = NotificationChannel::Email;
                $channels[] = NotificationChannel::Sms;
            }
        } else {
            // For participants/guardians: all channels by default
            $channels[] = NotificationChannel::Email;
            $channels[] = NotificationChannel::Sms;
        }

        // High/urgent priority: force all channels regardless of preferences
        if (in_array($priority, [NotificationPriority::High, NotificationPriority::Urgent])) {
            $channels = [NotificationChannel::InApp, NotificationChannel::Email, NotificationChannel::Sms];
        }

        return array_unique($channels);
    }

    /**
     * Resolve template: academy-specific first, then platform default (academy_id = null).
     */
    private function resolveTemplate(int $academyId, string $eventType, NotificationChannel $channel): ?NotificationTemplate
    {
        // Academy-specific first
        $template = NotificationTemplate::where('academy_id', $academyId)
            ->where('event_type', $eventType)
            ->where('channel', $channel)
            ->where('is_active', true)
            ->where('locale', 'ar')
            ->first();

        if ($template) {
            return $template;
        }

        // Platform default (academy_id = null)
        return NotificationTemplate::whereNull('academy_id')
            ->where('event_type', $eventType)
            ->where('channel', $channel)
            ->where('is_active', true)
            ->where('locale', 'ar')
            ->first();
    }

    /**
     * Render template body/subject by replacing {{variable}} placeholders.
     * Throws DomainException if required variables are missing or unreplaced placeholders remain.
     *
     * @return array{subject: ?string, body: string}
     */
    private function renderTemplate(NotificationTemplate $template, array $variables): array
    {
        $body = $template->body;
        $subject = $template->subject;

        // Validate all required variables are provided
        foreach ($template->variables as $varName) {
            if (!array_key_exists($varName, $variables)) {
                throw new DomainException("متغير مفقود في القالب: {$varName}");
            }
        }

        // Simple str_replace — no Blade
        foreach ($variables as $key => $value) {
            $body = str_replace('{{' . $key . '}}', (string) $value, $body);
            if ($subject) {
                $subject = str_replace('{{' . $key . '}}', (string) $value, $subject);
            }
        }

        // Verify no unreplaced variables remain
        if (preg_match('/\{\{[^}]+\}\}/', $body, $matches)) {
            throw new DomainException("متغير غير مستبدل في القالب: {$matches[0]}");
        }
        if ($subject && preg_match('/\{\{[^}]+\}\}/', $subject, $matches)) {
            throw new DomainException("متغير غير مستبدل في القالب: {$matches[0]}");
        }

        return ['subject' => $subject, 'body' => $body];
    }

    private function sendInApp(
        string $recipientType,
        int $recipientId,
        int $academyId,
        string $eventType,
        array $rendered
    ): void {
        $this->logNotification(
            $academyId, $recipientType, $recipientId, $eventType,
            NotificationChannel::InApp, $rendered['subject'], $rendered['body'],
            NotificationStatus::Sent
        );
    }

    private function sendEmail(
        ?string $email,
        array $rendered,
        string $recipientType,
        int $recipientId,
        int $academyId,
        string $eventType
    ): void {
        if (!$email) {
            $this->logNotification(
                $academyId, $recipientType, $recipientId, $eventType,
                NotificationChannel::Email, $rendered['subject'], $rendered['body'],
                NotificationStatus::Failed, 'No email address'
            );
            return;
        }

        Mail::raw($rendered['body'], function ($message) use ($email, $rendered) {
            $message->to($email)->subject($rendered['subject'] ?? 'إشعار');
        });

        $this->logNotification(
            $academyId, $recipientType, $recipientId, $eventType,
            NotificationChannel::Email, $rendered['subject'], $rendered['body'],
            NotificationStatus::Sent
        );
    }

    private function sendSms(
        ?string $phone,
        array $rendered,
        string $recipientType,
        int $recipientId,
        int $academyId,
        string $eventType
    ): void {
        if (!$phone) {
            $this->logNotification(
                $academyId, $recipientType, $recipientId, $eventType,
                NotificationChannel::Sms, null, $rendered['body'],
                NotificationStatus::Failed, 'No phone number'
            );
            return;
        }

        $formattedPhone = $this->formatPhone($phone);

        // Log gateway (no actual SMS provider configured yet)
        Log::info("[SMS] to {$formattedPhone}: {$rendered['body']}");

        $this->logNotification(
            $academyId, $recipientType, $recipientId, $eventType,
            NotificationChannel::Sms, null, $rendered['body'],
            NotificationStatus::Sent, null, ['phone' => $formattedPhone]
        );
    }

    /**
     * Format Egyptian phone number to E.164 (+20...).
     */
    private function formatPhone(string $phone): string
    {
        // Strip spaces and dashes
        $phone = preg_replace('/[\s\-]/', '', $phone);

        // Normalize to +20 prefix
        if (str_starts_with($phone, '0')) {
            $phone = '+20' . substr($phone, 1);
        } elseif (!str_starts_with($phone, '+')) {
            $phone = '+20' . $phone;
        }

        return $phone;
    }

    private function logNotification(
        int $academyId,
        string $recipientType,
        int $recipientId,
        string $eventType,
        NotificationChannel $channel,
        ?string $subject,
        string $body,
        NotificationStatus $status,
        ?string $error = null,
        ?array $metadata = null
    ): void {
        NotificationLog::create([
            'academy_id' => $academyId,
            'recipient_type' => $recipientType,
            'recipient_id' => $recipientId,
            'event_type' => $eventType,
            'channel' => $channel->value,
            'subject' => $subject,
            'body' => $body,
            'status' => $status->value,
            'error_message' => $error,
            'sent_at' => $status === NotificationStatus::Sent ? now() : null,
            'metadata' => $metadata ?? [],
        ]);
    }
}
