<?php

namespace App\Services\Notification;

use App\Libraries\NotificationChannels\EmailChannel;
use App\Libraries\NotificationChannels\InAppChannel;
use App\Libraries\NotificationChannels\SmsChannel;
use App\Libraries\NotificationChannels\WhatsAppChannel;
use App\Models\NotificationModel;
use App\Models\NotificationPreferenceModel;
use App\Models\NotificationTemplateModel;
use App\Models\UserModel;

/**
 * Every module calls NotificationService::send($userId, $triggerKey, $data)
 * the same way — this is the single fan-out point across channels, per
 * 05-saas-architecture.md §9. Templates are DB-editable (Super Admin can
 * change copy without a deploy); {placeholder} tokens in template bodies
 * are replaced from $data.
 */
class NotificationService
{
    private NotificationTemplateModel $templates;
    private NotificationModel $notifications;
    private NotificationPreferenceModel $preferences;
    private UserModel $userModel;

    public function __construct()
    {
        $this->templates     = new NotificationTemplateModel();
        $this->notifications = new NotificationModel();
        $this->preferences   = new NotificationPreferenceModel();
        $this->userModel     = new UserModel();
    }

    public function send(int $userId, string $triggerKey, array $data = []): void
    {
        $user = $this->userModel->find($userId);
        if (! $user) {
            return;
        }

        $prefs = $this->preferences->forUser($userId);
        $availableChannels = $this->templates->channelsFor($triggerKey);

        foreach ($availableChannels as $channel) {
            if (! $this->channelAllowed($channel, $prefs)) {
                continue;
            }

            $template = $this->templates->find_for($triggerKey, $channel);
            if (! $template) {
                continue;
            }

            $subject = $template['subject'] ? $this->interpolate($template['subject'], $data) : null;
            $body = $this->interpolate($template['body'], $data);

            $notificationId = $this->notifications->insert([
                'user_id'    => $userId,
                'type'       => $triggerKey,
                'payload'    => json_encode($data),
                'channel'    => $channel,
                'is_read'    => 0,
                'created_at' => date('Y-m-d H:i:s'),
            ], true);

            $recipient = $this->recipientFor($channel, $user);
            $sent = $recipient ? $this->channelAdapter($channel)->send($recipient, $subject, $body) : false;

            if ($sent) {
                $this->notifications->update($notificationId, ['sent_at' => date('Y-m-d H:i:s')]);
            }
        }
    }

    private function channelAllowed(string $channel, array $prefs): bool
    {
        return match ($channel) {
            'email'    => (bool) $prefs['email_enabled'],
            'sms'      => (bool) $prefs['sms_enabled'],
            'in_app'   => (bool) $prefs['in_app_enabled'],
            'whatsapp' => (bool) $prefs['whatsapp_enabled'],
            default    => false,
        };
    }

    private function recipientFor(string $channel, $user): ?string
    {
        return match ($channel) {
            'email'              => $user->email,
            'sms', 'whatsapp'    => $user->phone,
            'in_app'             => (string) $user->id, // unused by InAppChannel but kept for interface symmetry
            default              => null,
        };
    }

    private function channelAdapter(string $channel)
    {
        return match ($channel) {
            'email'    => new EmailChannel(),
            'sms'      => new SmsChannel(),
            'in_app'   => new InAppChannel(),
            'whatsapp' => new WhatsAppChannel(),
            default    => throw new \InvalidArgumentException("Unknown channel: {$channel}"),
        };
    }

    private function interpolate(string $template, array $data): string
    {
        $replacements = [];
        foreach ($data as $key => $value) {
            $replacements['{' . $key . '}'] = is_scalar($value) ? (string) $value : json_encode($value);
        }
        return strtr($template, $replacements);
    }

    public function markRead(int $notificationId, int $userId): bool
    {
        return $this->notifications->markRead($notificationId, $userId);
    }

    public function forUser(int $userId, bool $unreadOnly = false, int $page = 1, int $perPage = 20): array
    {
        return $this->notifications->forUser($userId, $unreadOnly, $page, $perPage);
    }

    public function updatePreferences(int $userId, array $prefs): void
    {
        $existing = $this->preferences->forUser($userId);
        $this->preferences->update($existing['id'], array_intersect_key($prefs, array_flip([
            'email_enabled', 'sms_enabled', 'in_app_enabled', 'whatsapp_enabled',
        ])));
    }
}
