<?php

namespace App\Services;

use App\Models\Admin;
use App\Models\Setting;
use App\Notifications\AdminInAppNotification;
use App\Services\NotificationTemplateService;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class NotificationService
{
    /**
     * Kanal şablonu (communication_templates satırı) ile sürülen kanallar —
     * render/skip kararı bu kümede uygulanır (PRC-621/PRC-622 review F5).
     */
    private const TEMPLATED_CHANNELS = ['slack', 'telegram', 'discord', 'teams', 'whatsapp', 'pushover', 'sms'];

    /**
     * Send admin notification via configured channels.
     *
     * @param string $type Notification type (provisioning_failed, ticket_created, etc.)
     * @param string $subject Short subject/title
     * @param string $message Full message body
     * @param array $data Extra data (entity_type, entity_id, url, etc.)
     * @param Admin|null $admin Specific admin, or null for all relevant admins
     */
    public function notifyAdmins(string $type, string $subject, string $message, array $data = [], ?Admin $admin = null): void
    {
        $channels = $this->getChannelsForType($type);

        if (empty($channels)) {
            return;
        }

        // Determine recipients
        $admins = $admin ? collect([$admin]) : $this->getAdminsForNotificationType($type);

        // Always create in-app notifications (independent of external channels)
        $this->createInAppNotifications($admins, $type, $subject, $message, $data);

        $mergeFields = $this->buildMergeFields($subject, $message, $data);
        $templateService = app(NotificationTemplateService::class);

        foreach ($admins as $recipient) {
            // Per-admin channel override: use admin's preferences if set, otherwise system defaults
            $adminChannels = $recipient->getNotificationChannels($type) ?? $channels;

            foreach ($adminChannels as $channel) {
                // Skip if admin doesn't have the required channel configured
                if ($channel === 'slack' && empty($recipient->slack_user_id)) continue;
                if ($channel === 'telegram' && empty($recipient->telegram_user_id)) continue;
                if ($channel === 'pushover' && empty($recipient->pushover_user_key)) continue;
                if ($channel === 'whatsapp' && empty($recipient->phone)) continue;
                if ($channel === 'sms' && empty($recipient->phone)) continue;
                if ($channel === 'discord' && empty($recipient->discord_webhook)) continue;
                if ($channel === 'teams' && empty($recipient->teams_webhook)) continue;

                try {
                    // PRC-622 review F5 — şablonlu kanallarda satır VARSA ama
                    // render null dönüyorsa (inactive veya içeriksiz) ham
                    // fallback YERİNE kanal atlanır: admin şablonu kapatarak
                    // kanalı susturabilmeli. Satır HİÇ yoksa eski ham fallback
                    // davranışı korunur (legacy type'lar kırılmasın).
                    if ($this->renderSkippedForInactiveTemplate($templateService, $channel, $type, $mergeFields, $rendered)) {
                        continue;
                    }

                    // PRC-624 — type'a özel şablon yoksa "Default Notification
                    // Message" şablonundan biçimle (varsa); o da yoksa ham metin.
                    $rendered = $this->defaultRender($templateService, $channel, $rendered, $mergeFields, "{$subject}\n{$message}");

                    if ($channel === 'slack') {
                        $this->sendRawSlackDm($recipient, $rendered);
                    } elseif ($channel === 'telegram') {
                        $this->sendRawTelegramDm($recipient, $rendered);
                    } elseif ($channel === 'whatsapp') {
                        $this->postToWhatsApp($recipient->phone, $rendered);
                    } elseif ($channel === 'pushover') {
                        $this->postToPushover($recipient->pushover_user_key, $subject, $rendered);
                    } elseif ($channel === 'discord') {
                        $this->postToDiscordWebhook($recipient->discord_webhook ?? '', $rendered);
                    } elseif ($channel === 'teams') {
                        $this->postToTeamsWebhook($recipient->teams_webhook ?? '', $rendered);
                    } elseif ($channel === 'sms') {
                        // PRC-621 — diğer kanallarla aynı desen: type-key adlı
                        // channel='sms' şablonu varsa render, yoksa Default Notification
                        // Message, o da yoksa ham fallback.
                        $this->sendSms($recipient, $rendered);
                    } else {
                        match ($channel) {
                            'email' => $this->sendEmail($recipient, $subject, $message, $data),
                            default => null,
                        };
                    }
                } catch (\Exception $e) {
                    Log::error("Notification failed: {$channel}", [
                        'type' => $type,
                        'admin' => $recipient->id,
                        'error' => $e->getMessage(),
                    ]);
                }
            }
        }
    }

    /**
     * Send system-level notification (not per-admin).
     */
    public function notifySystem(string $type, string $subject, string $message, array $data = []): void
    {
        // Always create in-app notifications for relevant admins
        $admins = $this->getAdminsForNotificationType($type);
        $this->createInAppNotifications($admins, $type, $subject, $message, $data);

        $channels = $this->getChannelsForType($type);

        $mergeFields = $this->buildMergeFields($subject, $message, $data);
        $templateService = app(NotificationTemplateService::class);

        foreach ($channels as $channel) {
            try {
                // PRC-622 review F5 — notifyAdmins ile aynı kural: şablon
                // satırı var ama render null (inactive/içeriksiz) → kanal atla.
                if ($this->renderSkippedForInactiveTemplate($templateService, $channel, $type, $mergeFields, $rendered)) {
                    continue;
                }

                // PRC-624 — type'a özel şablon yoksa "Default Notification
                // Message" şablonundan biçimle (varsa); o da yoksa ham metin.
                $rendered = $this->defaultRender($templateService, $channel, $rendered, $mergeFields, "{$subject}\n{$message}");

                if ($channel === 'slack') {
                    $slackChannel = Setting::get('slack_admin_channel', '#admin-notifications');
                    $this->postRawToSlackChannel($slackChannel, $rendered);
                } elseif ($channel === 'telegram') {
                    $chatId = Setting::get('telegram_admin_chat_id', '');
                    $this->postRawToTelegramChat($chatId, $rendered);
                } elseif ($channel === 'discord') {
                    $webhookUrl = Setting::get('discord_admin_webhook', '');
                    $this->postToDiscordWebhook($webhookUrl, $rendered);
                } elseif ($channel === 'teams') {
                    $webhookUrl = Setting::get('teams_admin_webhook', '');
                    $this->postToTeamsWebhook($webhookUrl, $rendered);
                } elseif ($channel === 'pushover') {
                    $userKey = Setting::get('pushover_admin_user_key', '');
                    $this->postToPushover($userKey, $subject, $rendered);
                } else {
                    match ($channel) {
                        'email' => $this->sendEmailToAddress(
                            Setting::get('admin_notification_email', config('mail.from.address')),
                            $subject,
                            $message
                        ),
                        default => null,
                    };
                }
            } catch (\Exception $e) {
                Log::error("System notification failed: {$channel}", [
                    'type' => $type,
                    'error' => $e->getMessage(),
                ]);
            }
        }
    }

    /**
     * PRC-624 — Default Notification Message genellemesi. Type'a özel şablon
     * satırı YOKSA ($rendered null geldiyse) ham {subject}/{message} göndermek
     * yerine, kanonik "Default Notification Message" şablonu VARSA onu
     * {SUBJECT}/{MESSAGE} merge field'larıyla render eder. Şablon yoksa eski
     * ham fallback metnini döndürür (geriye uyumlu). Bu bir özel-case değil
     * GENELLEME: tüm template'siz admin bildirimleri tek şablondan biçimlenir.
     *
     * $rendered : type'a özel şablon render'ı (null ise default'a düşülür)
     * $rawText  : default şablon da yoksa kullanılacak ham metin
     */
    private function defaultRender(
        NotificationTemplateService $templateService,
        string $channel,
        ?string $rendered,
        array $mergeFields,
        string $rawText
    ): string {
        if ($rendered !== null) {
            return $rendered;
        }

        $default = $templateService->render($channel, self::DEFAULT_TEMPLATE_NAME, $mergeFields);

        return $default ?? $rawText;
    }

    /**
     * Kanonik fallback şablonu adı (7 kanalda seed'li, K1). Type'a özel satırı
     * olmayan admin bildirimleri bu şablondan biçimlenir.
     */
    public const DEFAULT_TEMPLATE_NAME = 'Default Notification Message';

    /**
     * PRC-622 review F5 — şablonlu kanal için render dene; $rendered out-param
     * olarak döner. true = kanal ATLANMALI: bu kanal+ad için şablon SATIRI var
     * ama render null döndü (satır inactive ya da dil içeriği boş) — admin
     * şablonu kapatarak kanalı susturmuştur, ham fallback gönderilmez.
     * Satır hiç yoksa false döner ve $rendered null kalır → çağıran eski ham
     * fallback davranışını sürdürür (legacy type'lar kırılmaz).
     */
    private function renderSkippedForInactiveTemplate(
        NotificationTemplateService $templateService,
        string $channel,
        string $type,
        array $mergeFields,
        ?string &$rendered
    ): bool {
        $rendered = null;

        if (! in_array($channel, self::TEMPLATED_CHANNELS, true)) {
            return false;
        }

        $rendered = $templateService->render($channel, $type, $mergeFields);

        if ($rendered === null && $templateService->hasTemplateRow($channel, $type)) {
            Log::info('NotificationService: template row inactive or empty, channel skipped', [
                'channel' => $channel,
                'type' => $type,
            ]);

            return true;
        }

        return false;
    }

    /**
     * PRC-624 — Bildirim tipi (= şablon adı, artık Title Case olabilir) →
     * tercih anahtarı. Settings key'leri ('notification_channels_*') ve
     * admins.notification_preferences JSON key'leri HTML form adlarında
     * güvenli kalsın diye snake-slug formda saklanır; eski snake type'lar
     * için slug no-op'tur ('provisioning_failed' → 'provisioning_failed').
     */
    public static function prefKey(string $type): string
    {
        return \Illuminate\Support\Str::slug($type, '_');
    }

    /**
     * Get configured channels for a notification type.
     */
    private function getChannelsForType(string $type): array
    {
        // Check type-specific setting first, then fallback to default
        $channels = Setting::get('notification_channels_' . self::prefKey($type));

        if ($channels) {
            return array_filter(explode(',', $channels));
        }

        // Fallback: default admin notification channels
        $default = Setting::get('admin_notification_channels', 'email');
        return array_filter(explode(',', $default));
    }

    /**
     * Get admins who should receive this notification type.
     */
    private function getAdminsForNotificationType(string $type): \Illuminate\Support\Collection
    {
        // PRC-624 — Title Case adlar için prefix kontrolleri slug üzerinden
        // ('Provisioning Failed' → 'provisioning_failed').
        $key = self::prefKey($type);

        // For provisioning failures: admins with 'manage queue' permission
        if (str_starts_with($key, 'provisioning_')) {
            return Admin::permission('manage queue')->where('status', 'active')->get();
        }

        // For verification events: admins with 'approve verifications' permission
        if (str_starts_with($key, 'verification_')) {
            return Admin::permission('approve verifications')->where('status', 'active')->get();
        }

        // Default: all active admins
        return Admin::where('status', 'active')->where('is_deleted', false)->limit(10)->get();
    }

    private function sendEmail(Admin $admin, string $subject, string $message, array $data): void
    {
        \App\Jobs\SendEmailJob::dispatch(
            'Admin Notification',
            $data['entity_type'] ?? 'system',
            $data['entity_id'] ?? 0,
            array_merge([
                'SUBJECT' => $subject,
                'NOTIFICATION_MESSAGE' => $message,
                'ADMIN_NAME' => $admin->name,
            ], $data),
            null,
            $admin->email
        );
    }

    private function sendEmailToAddress(string $email, string $subject, string $message): void
    {
        if (empty($email)) return;

        \App\Jobs\SendEmailJob::dispatch(
            'Admin Notification',
            'system',
            0,
            [
                'SUBJECT' => $subject,
                'NOTIFICATION_MESSAGE' => $message,
            ],
            null,
            $email
        );
    }

    private function sendSms(Admin $admin, string $text): void
    {
        $phone = $admin->phone ?? null;
        if (!$phone) return;

        try {
            // SmsMessage log'u SmsService::send içinde merkezî (PRC-621);
            // admin alıcıda client_id null kalır.
            app(SmsService::class)->send($phone, $text);
        } catch (\Exception $e) {
            Log::error('SMS notification failed', ['admin' => $admin->id, 'error' => $e->getMessage()]);
        }
    }

    private function sendSlackDm(Admin $admin, string $subject, string $message, array $data): void
    {
        $slackUserId = $admin->slack_user_id;
        if (!$slackUserId) return;

        $botToken = Setting::get('slack_bot_token', '');
        if (empty($botToken)) return;

        $url = $data['url'] ?? '';
        $text = "*{$subject}*\n{$message}" . ($url ? "\n<{$url}|View Details>" : '');

        Http::withToken($botToken)
            ->post('https://slack.com/api/chat.postMessage', [
                'channel' => $slackUserId, // DM to user
                'text' => $text,
            ]);
    }

    private function sendTelegramDm(Admin $admin, string $subject, string $message, array $data): void
    {
        $telegramUserId = $admin->telegram_user_id;
        if (!$telegramUserId) return;

        $botToken = Setting::get('telegram_bot_token', '');
        if (empty($botToken)) return;

        $url = $data['url'] ?? '';
        $text = "*{$subject}*\n{$message}" . ($url ? "\n[View Details]({$url})" : '');

        Http::post("https://api.telegram.org/bot{$botToken}/sendMessage", [
            'chat_id' => $telegramUserId,
            'text' => $text,
            'parse_mode' => 'Markdown',
        ]);
    }

    private function postToSlackChannel(string $channel, string $subject, string $message, array $data): void
    {
        $botToken = Setting::get('slack_bot_token', '');
        if (empty($botToken) || empty($channel)) return;

        $url = $data['url'] ?? '';
        $emoji = $data['emoji'] ?? ':warning:';
        $text = "{$emoji} *{$subject}*\n{$message}" . ($url ? "\n<{$url}|View Details>" : '');

        Http::withToken($botToken)
            ->post('https://slack.com/api/chat.postMessage', [
                'channel' => $channel,
                'text' => $text,
            ]);
    }

    private function postToTelegramChat(string $chatId, string $subject, string $message, array $data): void
    {
        $botToken = Setting::get('telegram_bot_token', '');
        if (empty($botToken) || empty($chatId)) return;

        $emoji = $data['emoji'] ?? '⚠️';
        $text = "{$emoji} *{$subject}*\n{$message}";

        Http::post("https://api.telegram.org/bot{$botToken}/sendMessage", [
            'chat_id' => $chatId,
            'text' => $text,
            'parse_mode' => 'Markdown',
        ]);
    }

    /**
     * Build merge fields from subject, message, and data array.
     * Converts data array keys to uppercase merge field format.
     */
    private function buildMergeFields(string $subject, string $message, array $data): array
    {
        $fields = ['SUBJECT' => $subject, 'MESSAGE' => $message];
        foreach ($data as $key => $value) {
            if (is_string($value) || is_numeric($value)) {
                $fields[strtoupper($key)] = $value;
            }
        }
        return $fields;
    }

    /**
     * Post pre-formatted content to a Slack channel (no subject/emoji prepending).
     */
    private function postRawToSlackChannel(string $channel, string $text): void
    {
        $botToken = Setting::get('slack_bot_token', '');
        if (empty($botToken) || empty($channel)) return;

        Http::withToken($botToken)
            ->post('https://slack.com/api/chat.postMessage', [
                'channel' => $channel,
                'text' => $text,
            ]);
    }

    /**
     * Post pre-formatted content to a Telegram chat (no subject/emoji prepending).
     */
    private function postRawToTelegramChat(string $chatId, string $text): void
    {
        $botToken = Setting::get('telegram_bot_token', '');
        if (empty($botToken) || empty($chatId)) return;

        Http::post("https://api.telegram.org/bot{$botToken}/sendMessage", [
            'chat_id' => $chatId,
            'text' => $text,
            'parse_mode' => 'Markdown',
        ]);
    }

    /**
     * Send pre-formatted content as a Slack DM to an admin (no subject prepending).
     */
    private function sendRawSlackDm(Admin $admin, string $text): void
    {
        $slackUserId = $admin->slack_user_id;
        if (!$slackUserId) return;

        $botToken = Setting::get('slack_bot_token', '');
        if (empty($botToken)) return;

        Http::withToken($botToken)
            ->post('https://slack.com/api/chat.postMessage', [
                'channel' => $slackUserId,
                'text' => $text,
            ]);
    }

    /**
     * Send pre-formatted content as a Telegram DM to an admin (no subject prepending).
     */
    private function sendRawTelegramDm(Admin $admin, string $text): void
    {
        $telegramUserId = $admin->telegram_user_id;
        if (!$telegramUserId) return;

        $botToken = Setting::get('telegram_bot_token', '');
        if (empty($botToken)) return;

        Http::post("https://api.telegram.org/bot{$botToken}/sendMessage", [
            'chat_id' => $telegramUserId,
            'text' => $text,
            'parse_mode' => 'Markdown',
        ]);
    }

    /**
     * Post a message to a Discord webhook URL.
     */
    private function postToDiscordWebhook(string $webhookUrl, string $text): void
    {
        if (empty($webhookUrl)) return;

        Http::post($webhookUrl, [
            'content' => $text,
        ]);
    }

    /**
     * Post a message to a Microsoft Teams webhook URL (PRC-782).
     *
     * Teams "Workflows" (Power Automate) incoming webhook'u basit `{"text": "..."}`
     * JSON'ı kabul eder (Markdown destekli). Adaptive Card sarmalamaya gerek yok.
     * Boş URL'de sessiz skip. URL'ler SAS imzalı olduğundan çok uzun olabilir
     * (kolon string(1024), validation max:2048).
     */
    private function postToTeamsWebhook(string $webhookUrl, string $text): void
    {
        if (empty($webhookUrl)) return;

        Http::post($webhookUrl, [
            'text' => $text,
        ]);
    }

    /**
     * Send a message via WhatsApp Business API.
     */
    private function postToWhatsApp(string $phoneNumber, string $text): void
    {
        $token = Setting::get('whatsapp_api_token', '');
        $phoneNumberId = Setting::get('whatsapp_phone_number_id', '');
        if (empty($token) || empty($phoneNumberId) || empty($phoneNumber)) return;

        Http::withToken($token)->post("https://graph.facebook.com/v18.0/{$phoneNumberId}/messages", [
            'messaging_product' => 'whatsapp',
            'to' => $phoneNumber,
            'type' => 'text',
            'text' => ['body' => $text],
        ]);
    }

    /**
     * Send a notification via Pushover API.
     */
    private function postToPushover(string $userKey, string $subject, string $text): void
    {
        $appToken = Setting::get('pushover_app_token', '');
        if (empty($appToken) || empty($userKey)) return;

        Http::post('https://api.pushover.net/1/messages.json', [
            'token' => $appToken,
            'user' => $userKey,
            'title' => $subject,
            'message' => $text,
            'html' => 1,
        ]);
    }

    /**
     * Create in-app notifications for admins and invalidate their caches.
     */
    private function createInAppNotifications(Collection $admins, string $type, string $subject, string $message, array $data): void
    {
        $severity = AdminInAppNotification::severityForType($type);
        $icon = $data['icon'] ?? AdminInAppNotification::iconForType($type);
        $url = $data['url'] ?? null;
        $extra = array_filter([
            'entity_type' => $data['entity_type'] ?? null,
            'entity_id' => $data['entity_id'] ?? null,
        ]);

        foreach ($admins as $admin) {
            try {
                $admin->notify(new AdminInAppNotification(
                    notificationType: $type,
                    title: $subject,
                    message: $message,
                    severity: $severity,
                    url: $url,
                    icon: $icon,
                    extra: $extra,
                ));

                $this->invalidateNotificationCache($admin->id);
            } catch (\Exception $e) {
                Log::error('In-app notification failed', [
                    'admin_id' => $admin->id,
                    'type' => $type,
                    'error' => $e->getMessage(),
                ]);
            }
        }
    }

    /**
     * Invalidate notification cache for an admin.
     */
    private function invalidateNotificationCache(int $adminId): void
    {
        Cache::forget("admin:{$adminId}:notifications:unread_count");
        Cache::forget("admin:{$adminId}:notifications:latest");
    }
}
