<?php
// +----------------------------------------------------------------------
// | onezan-admin 快速开发的管理平台
// +----------------------------------------------------------------------
// | 官方网址：https://www.onezan.cn
// +----------------------------------------------------------------------
// | onezan团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: 543885307@qq.com
// +----------------------------------------------------------------------
namespace app\service\domain\notification;

use Core\service\registry\MessageRegistry;
use app\model\User;
use app\model\UserChannelAccount;
use app\repository\channel\ChannelMessageConfigRepository;
use app\repository\channel\ChannelRepository;
use app\service\domain\channel\CoreChannelCatalogService;
use app\service\domain\notification\driver\EmailDriver;
use app\service\domain\notification\driver\NotificationDriverInterface;
use app\service\domain\notification\driver\SmsDriver;
use app\service\domain\notification\driver\WeappSubscribeDriver;
use app\service\domain\notification\driver\WechatTemplateDriver;
use think\App;
use think\facade\Log;


/**
 * Notification Dispatcher
 * @package app\service\domain\notification
 */
class NotificationDispatcher
{
    private App $app;
    private MessageRegistry $messageRegistry;

    /**
     * @var array<string, NotificationDriverInterface>
     */
    private array $drivers = [];

    /**
     * 构造函数
     * @param App $app 应用实例
     * @param MessageRegistry $messageRegistry 消息注册表
     */
    public function __construct(App $app, MessageRegistry $messageRegistry)
    {
        $this->app = $app;
        $this->messageRegistry = $messageRegistry;
        $this->registerDrivers();
    }


    /**
     * 根据消息编码向指定用户发送通知（按渠道及驱动分发）
     * @param string $messageCode 消息编码
     * @param int $userId 目标用户ID
     * @param array $data 消息模板数据
     * @return array 发送结果详情
     */
    public function dispatch(string $messageCode, int $userId, array $data): array
    {
        $messageDef = $this->resolveMessageDef($messageCode);
        if (!$messageDef) {
            Log::warning('' . $messageCode);
            return ['code' => 0, 'details' => []];
        }

        $scene = (string)($messageDef['scene'] ?? '');
        $driverTypes = $messageDef['driver_types'] ?? [];

        if ($scene === '' || empty($driverTypes)) {
            Log::warning('' . $messageCode);
            return ['code' => 0, 'details' => []];
        }

        $accounts = $this->userChannelAccounts($userId);
        $details = [];
        $user = User::find($userId);
        $userPhone = $user ? (string)($user->phone ?? '') : '';
        $userEmail = $user ? (string)($user->email ?? '') : '';
        $channelCatalog = $this->catalog()->channels();
        $notifyDriverEntries = $this->catalog()->notifyDrivers();

        $directDrivers = $this->collectDirectDrivers($driverTypes);
        $hasDirectDrivers = !empty($directDrivers);

        if (empty($accounts)) {
            if (!$hasDirectDrivers) {
                return ['code' => 0, 'details' => [['result' => '']]];
            }
            return $this->dispatchDirect($scene, $directDrivers, $driverTypes, $notifyDriverEntries, $data, $userPhone, $userEmail, $messageCode);
        }

        foreach ($accounts as $account) {
            $channel = (string)($account['channel'] ?? '');
            if ($channel === '') {
                continue;
            }

            if (!$this->isMessageEnabled($channel, $messageCode)) {
                $details[] = ['channel' => $channel, 'result' => ''];
                continue;
            }

            $channelMeta = $channelCatalog[$channel] ?? null;
            if (!$channelMeta) {
                $details[] = ['channel' => $channel, 'result' => ''];
                continue;
            }

            $matchedDrivers = $this->matchDrivers(
                (array)($channelMeta['notify_drivers'] ?? []),
                $notifyDriverEntries,
                $scene,
                $driverTypes
            );

            if (empty($matchedDrivers)) {
                $details[] = ['channel' => $channel, 'result' => ''];
                continue;
            }

            foreach ($matchedDrivers as $driverKey => $driverDef) {
                $driverType = (string)($driverDef['driver_type'] ?? '');
                $driver = $this->drivers[$driverType] ?? null;
                if (!$driver) {
                    $details[] = ['channel' => $channel, 'driver' => $driverType, 'result' => ''];
                    continue;
                }

                $notifyConfig = $this->loadNotifyConfig($channel, $driverKey);
                if (empty($notifyConfig['template_id'])) {
                    $details[] = ['channel' => $channel, 'driver' => $driverType, 'result' => ''];
                    continue;
                }

                $to = $this->buildRecipient($driverType, $account, $userPhone, $userEmail);
                $result = $driver->send($to, (string)$notifyConfig['template_id'], $data, $notifyConfig);
                $details[] = [
                    'channel' => $channel,
                    'driver' => $driverType,
                    'result' => (int)($result['code'] ?? 0) === 0 ? 'ok' : (string)($result['msg'] ?? 'failed'),
                    'raw' => $result['raw'] ?? [],
                ];
            }
        }

        if ($hasDirectDrivers) {
            $directDetails = $this->dispatchDirect($scene, $directDrivers, $driverTypes, $notifyDriverEntries, $data, $userPhone, $userEmail, $messageCode);
            $details = array_merge($details, $directDetails['details'] ?? []);
        }

        return ['code' => 0, 'details' => $details];
    }


    /**
     * 匹配渠道支持的驱动与消息场景/驱动类型
     * @param array $channelDrivers 渠道已注册的驱动Key列表
     * @param array $entries 通知驱动目录条目
     * @param string $scene 消息场景
     * @param array $driverTypes 消息定义的驱动类型列表
     * @return array 匹配到的驱动条目
     */
    private function matchDrivers(array $channelDrivers, array $entries, string $scene, array $driverTypes): array
    {
        $matched = [];
        $allowedTypes = array_map('strval', $driverTypes);

        foreach ($channelDrivers as $driverKey) {
            $driverKey = (string)$driverKey;
            $entry = $entries[$driverKey] ?? null;
            if (!$entry) {
                continue;
            }

            $entryScene = (string)($entry['scene_key'] ?? '');
            $entryDriverType = (string)($entry['driver_type'] ?? '');

            if ($entryScene === $scene && in_array($entryDriverType, $allowedTypes, true)) {
                $matched[$driverKey] = $entry;
            }
        }

        return $matched;
    }

    /**
     * 根据消息编码从注册表中解析消息定义
     * @param string $messageCode 消息编码
     * @return array|null 消息定义数组或null
     */
    private function resolveMessageDef(string $messageCode): ?array
    {
        foreach ($this->messageRegistry->all() as $item) {
            if ((string)($item['code'] ?? '') === $messageCode) {
                return $item;
            }
        }
        return null;
    }

    /**
     * 获取用户所有已启用的渠道绑定账号
     * @param int $userId 用户ID
     * @return array 渠道账号列表
     */
    private function userChannelAccounts(int $userId): array
    {
        return UserChannelAccount::where('user_id', $userId)
            ->where('status', 1)
            ->select()
            ->toArray();
    }

    /**
     * 判断指定渠道是否启用了某消息类型
     * @param string $channel 渠道标识
     * @param string $messageCode 消息编码
     * @return bool 是否启用
     */
    private function isMessageEnabled(string $channel, string $messageCode): bool
    {
        $rows = $this->messageConfigRepository()->listByChannel($channel);
        foreach ($rows as $row) {
            if ((string)($row['message_code'] ?? '') === $messageCode) {
                return (int)($row['status'] ?? 0) === 1;
            }
        }
        return true;
    }

    /**
     * 加载指定渠道下某个驱动Key的通知配置
     * @param string $channel 渠道标识
     * @param string $driverKey 驱动Key
     * @return array 通知配置数组
     */
    private function loadNotifyConfig(string $channel, string $driverKey): array
    {
        $rows = $this->channelRepository()->listNotifyConfigs($channel);

        foreach ($rows as $row) {
            if ((string)($row['driver'] ?? '') === $driverKey) {
                $configValue = json_decode((string)($row['config_value'] ?? '{}'), true);
                if (is_array($configValue)) {
                    return $configValue;
                }
            }
        }

        return [];
    }

    /**
     * 根据驱动类型构建收件人信息
     * @param string $driverType 驱动类型（sms/email/weapp_subscribe/wechat_template）
     * @param array $account 渠道账号信息
     * @param string $userPhone 用户手机号（备用）
     * @param string $userEmail 用户邮箱（备用）
     * @return array 收件人信息数组
     */
    private function buildRecipient(string $driverType, array $account, string $userPhone, string $userEmail): array
    {
        switch ($driverType) {
            case 'weapp_subscribe':
            case 'wechat_template':
                return ['openid' => (string)($account['openid'] ?? '')];
            case 'sms':
                return ['phone' => $userPhone !== '' ? $userPhone : (string)($account['phone'] ?? '')];
            case 'email':
                return ['email' => $userEmail !== '' ? $userEmail : (string)($account['email'] ?? '')];
            default:
                return [];
        }
    }


    /**
     * 收集无需渠道绑定的直连驱动类型（sms/email）
     * @param array $driverTypes 驱动类型列表
     * @return array 直连驱动类型列表
     */
    private function collectDirectDrivers(array $driverTypes): array
    {
        $direct = ['sms', 'email'];
        // In debug mode, also treat weapp_subscribe/wechat_template as direct (mock)
        if ($this->app->isDebug()) {
            $direct[] = 'weapp_subscribe';
            $direct[] = 'wechat_template';
        }
        return array_values(array_intersect($driverTypes, $direct));
    }


    /**
     * 直接发送短信/邮件通知（无需渠道绑定）
     * @param string $scene 消息场景
     * @param array $directDrivers 直连驱动类型列表
     * @param array $driverTypes 所有驱动类型
     * @param array $notifyDriverEntries 通知驱动目录
     * @param array $data 模板变量数据
     * @param string $userPhone 用户手机号
     * @param string $userEmail 用户邮箱
     * @param string $messageCode 消息编码
     * @return array 发送结果
     */
    private function dispatchDirect(string $scene, array $directDrivers, array $driverTypes, array $notifyDriverEntries, array $data, string $userPhone, string $userEmail, string $messageCode): array
    {
        $details = [];

        foreach ($directDrivers as $driverType) {
            $driver = $this->drivers[$driverType] ?? null;
            if (!$driver) {
                continue;
            }

            $driverKey = $scene . '.' . $driverType;
            $fallbackChannel = 'h5';
            $notifyConfig = $this->loadNotifyConfig($fallbackChannel, $driverKey);

            if (empty($notifyConfig)) {
                $notifyConfig = $this->loadNotifyConfigFromAnyChannel($driverKey);
            }

            if (empty($notifyConfig['template_id'])) {
                $details[] = ['channel' => '', 'driver' => $driverType, 'result' => ''];
                continue;
            }

            $to = [];
            if ($driverType === 'sms') {
                if ($userPhone === '') {
                    $details[] = ['channel' => '', 'driver' => $driverType, 'result' => ''];
                    continue;
                }
                $to = ['phone' => $userPhone];
            } elseif ($driverType === 'email') {
                if ($userEmail === '') {
                    $details[] = ['channel' => '', 'driver' => $driverType, 'result' => ''];
                    continue;
                }
                $to = ['email' => $userEmail];
            }

            $result = $driver->send($to, (string)$notifyConfig['template_id'], $data, $notifyConfig);
            $details[] = [
                'channel' => '',
                'driver' => $driverType,
                'result' => (int)($result['code'] ?? 0) === 0 ? 'ok' : (string)($result['msg'] ?? 'failed'),
                'raw' => $result['raw'] ?? [],
            ];
        }

        return ['code' => 0, 'details' => $details];
    }


    /**
     * 从任意渠道查找驱动通知配置（自上而下遍历）
     * @param string $driverKey 驱动Key
     * @return array 通知配置数组
     */
    private function loadNotifyConfigFromAnyChannel(string $driverKey): array
    {
        $channelCatalog = $this->catalog()->channels();
        foreach (array_keys($channelCatalog) as $channel) {
            $config = $this->loadNotifyConfig($channel, $driverKey);
            if (!empty($config)) {
                return $config;
            }
        }
        return [];
    }

    /**
     * 注册所有通知驱动实例
     * @return void
     */
    private function registerDrivers(): void
    {
        $this->drivers = [
            'weapp_subscribe' => new WeappSubscribeDriver($this->app),
            'wechat_template' => new WechatTemplateDriver($this->app),
            'sms' => new SmsDriver($this->app),
            'email' => new EmailDriver($this->app),
        ];
    }

    /**
     * 获取渠道消息配置仓储实例
     * @return ChannelMessageConfigRepository
     */
    private function messageConfigRepository(): ChannelMessageConfigRepository
    {
        return $this->app->make(ChannelMessageConfigRepository::class);
    }

    /**
     * 获取渠道仓储实例
     * @return ChannelRepository
     */
    private function channelRepository(): ChannelRepository
    {
        return $this->app->make(ChannelRepository::class);
    }

    /**
     * 获取渠道目录服务实例
     * @return CoreChannelCatalogService
     */
    private function catalog(): CoreChannelCatalogService
    {
        return $this->app->make(CoreChannelCatalogService::class);
    }
}