<?php
namespace Core;

use App\Models\PSI\Event;
use App\Models\Setting;

/**
 * PSI / 站点事件通知服务
 * --------------------------------------------------
 * 当 PSI 内出现「新订单 / 新事件」时统一调用本服务：
 *   1) 记录一条“紧急事件”到 psi_events（站内提醒中心，所有相关人员可见）
 *   2) （可选）邮件通知负责人（SMTP / mail()）
 *   3) （可选）企业微信群机器人（webhook）通知负责人并 @ 提醒
 *
 * 邮件/微信是否发送取决于“通知设置”（Settings）：
 *   notify_enabled              总开关
 *   notify_email_enabled        邮件开关
 *   notify_email_smtp_host      发信服务器（留空则用 mail()）
 *   notify_email_smtp_port      端口（465=SSL / 587=STARTTLS）
 *   notify_email_smtp_user      账号
 *   notify_email_smtp_pass      密码
 *   notify_email_from           发件人
 *   notify_email_to             负责人邮箱（逗号分隔）
 *   notify_wechat_enabled       企业微信开关
 *   notify_wechat_webhook       企业微信机器人 webhook 地址
 *   notify_wechat_mention       被@的手机号/英文id（逗号分隔）
 *   notify_lowstock_enabled     低库存提醒开关
 *   notify_lowstock_threshold   低库存阈值
 */
class Notify
{
    /** 触发一个事件（默认紧急）。自动记录站内提醒并推送渠道。 */
    public static function fire(string $type, string $title, string $body, array $opts = []): void
    {
        try {
            self::ensureTable();

            $level  = $opts['level']  ?? 'urgent';
            $url    = $opts['url']    ?? '';
            $refNo  = $opts['ref_no'] ?? '';
            $sys    = $opts['sys']    ?? 'psi';

            $recipients = self::recipients();
            $channels   = ['inapp'];

            // ① 邮件
            if (self::emailEnabled() && $recipients['email']) {
                if (self::sendEmail($recipients['email'], $title, $body, $url, $level)) {
                    $channels[] = 'email';
                }
            }
            // ② 企业微信
            if (self::wechatEnabled()) {
                if (self::sendWeChat($title, $body, $url, $recipients['wechat'])) {
                    $channels[] = 'wechat';
                }
            }

            // ③ 站内紧急事件（始终记录，即使渠道未配置）
            (new Event())->insert([
                'sys'        => $sys,
                'type'       => $type,
                'level'      => $level,
                'title'      => $title,
                'body'       => $body,
                'url'        => $url,
                'ref_no'     => $refNo,
                'recipients' => json_encode($recipients, JSON_UNESCAPED_UNICODE),
                'channels'   => json_encode(array_values(array_unique($channels)), JSON_UNESCAPED_UNICODE),
                'read_by'    => json_encode([], JSON_UNESCAPED_UNICODE),
                'created_at' => date('Y-m-d H:i:s'),
            ]);
        } catch (\Throwable $e) {
            error_log('[Notify] fire failed: ' . $e->getMessage());
        }
    }

    /* ============== 业务便捷方法 ============== */

    /** 新销售订单 */
    public static function newSalesOrder(string $orderNo, string $customer, string $salesman, int $id): void
    {
        $title = "【紧急】新销售订单待跟进：{$orderNo}";
        $body  = "客户：{$customer}\n负责人：{$salesman}\n订单号：{$orderNo}\n请尽快处理并安排发货。";
        self::fire('sales_order', $title, $body, [
            'level'  => 'urgent', 'ref_no' => $orderNo, 'url' => "PSI/sales_orders/show/{$id}",
        ]);
    }

    /** 新采购订单 */
    public static function newPurchaseOrder(string $orderNo, string $supplier, string $buyer, int $id): void
    {
        $title = "【紧急】新采购订单待处理：{$orderNo}";
        $body  = "供应商：{$supplier}\n采购人：{$buyer}\n订单号：{$orderNo}\n请尽快审核并安排收货。";
        self::fire('purchase_order', $title, $body, [
            'level'  => 'urgent', 'ref_no' => $orderNo, 'url' => "PSI/purchase_orders/show/{$id}",
        ]);
    }

    /** 新客户订单（来自前台网站下单） */
    public static function newCustomerOrder(string $orderNo, string $customer, string $phone, int $id): void
    {
        $title = "【紧急】收到新客户订单：{$orderNo}";
        $body  = "客户：{$customer}\n电话：{$phone}\n订单号：{$orderNo}\n请尽快联系客户并安排发货。";
        self::fire('customer_order', $title, $body, [
            'level'  => 'urgent', 'ref_no' => $orderNo, 'url' => "PSI/orders/show/{$id}",
        ]);
    }

    /** 低库存预警（库存跌破阈值时触发） */
    public static function lowStockEvent(string $itemType, string $name, float $stock, float $threshold, int $itemId): void
    {
        $kind  = $itemType === 'product' ? '成品' : '物料';
        $title = "【紧急】{$kind}库存不足：{$name}";
        $body  = "{$kind}：{$name}\n当前库存：{$stock}\n预警阈值：{$threshold}\n请及时补货。";
        self::fire('low_stock', $title, $body, [
            'level'  => 'urgent', 'ref_no' => $name, 'url' => "PSI/stock",
        ]);
    }

    /* ============== 收件人与开关 ============== */

    private static function recipients(): array
    {
        $s = new Setting();
        $emailTo = trim((string) $s->get('notify_email_to', ''), " \t\n\r,");
        $wechat  = trim((string) $s->get('notify_wechat_mention', ''), " \t\n\r,");
        return [
            'email' => $emailTo === '' ? [] : array_filter(array_map('trim', explode(',', $emailTo))),
            'wechat' => $wechat === '' ? [] : array_filter(array_map('trim', explode(',', $wechat))),
        ];
    }

    private static function masterEnabled(): bool
    {
        return (int) (new Setting())->get('notify_enabled', 0) === 1;
    }

    private static function emailEnabled(): bool
    {
        if (!self::masterEnabled()) return false;
        return (int) (new Setting())->get('notify_email_enabled', 0) === 1;
    }

    private static function wechatEnabled(): bool
    {
        if (!self::masterEnabled()) return false;
        return (int) (new Setting())->get('notify_wechat_enabled', 0) === 1
            && trim((string) (new Setting())->get('notify_wechat_webhook', '')) !== '';
    }

    public static function lowStockEnabled(): bool
    {
        return (int) (new Setting())->get('notify_lowstock_enabled', 1) === 1;
    }

    public static function lowStockThreshold(): float
    {
        return (float) (new Setting())->get('notify_lowstock_threshold', 20);
    }

    /* ============== 邮件发送 ============== */

    private static function sendEmail(array $to, string $subject, string $body, string $url, string $level): bool
    {
        $s     = new Setting();
        $host  = trim((string) $s->get('notify_email_smtp_host', ''));
        $port  = (int) $s->get('notify_email_smtp_port', 465);
        $user  = trim((string) $s->get('notify_email_smtp_user', ''));
        $pass  = trim((string) $s->get('notify_email_smtp_pass', ''));
        $from  = trim((string) $s->get('notify_email_from', ''));
        if ($from === '') $from = $user;
        $html  = self::emailHtml($subject, $body, $url, $level);

        if ($host !== '' && $user !== '') {
            $scheme = ($port === 465) ? 'ssl' : 'tls';
            return self::smtpSend($host, $port, $scheme, $user, $pass, $from, $to, $subject, $html);
        }

        // 回退：PHP 内置 mail()
        $headers  = "MIME-Version: 1.0\r\n";
        $headers .= "Content-Type: text/html; charset=UTF-8\r\n";
        $headers .= "From: {$from}\r\n";
        $ok = true;
        foreach ($to as $t) {
            if (!@mail($t, '=?UTF-8?B?' . base64_encode($subject) . '?=', $html, $headers)) $ok = false;
        }
        return $ok;
    }

    private static function emailHtml(string $subject, string $body, string $url, string $level): string
    {
        $lines = nl2br(htmlspecialchars($body, ENT_QUOTES, 'UTF-8'));
        $link  = $url ? App::url($url) : '';
        $urgent = $level === 'urgent' ? '<span style="color:#dc2626;font-weight:700;">紧急事件</span>' : '通知';
        return <<<HTML
<!doctype html><html lang="zh-CN"><body style="margin:0;background:#f3f4f6;font-family:-apple-system,'Segoe UI',Roboto,'PingFang SC','Microsoft YaHei',sans-serif;">
<div style="max-width:560px;margin:24px auto;background:#fff;border-radius:14px;overflow:hidden;box-shadow:0 8px 30px rgba(15,42,68,.12);">
  <div style="background:linear-gradient(135deg,#0ea5e9,#14b8a6);padding:18px 22px;color:#fff;font-size:16px;font-weight:700;">酷冰甲 · PSI 进销存 {$urgent}</div>
  <div style="padding:22px;color:#0f2a44;font-size:15px;line-height:1.8;">
    <div style="font-size:16px;font-weight:700;margin-bottom:10px;">{$subject}</div>
    <div style="color:#334155;">{$lines}</div>
    {$link}
  </div>
  <div style="padding:0 22px 20px;">
    <a href="{$link}" style="display:inline-block;padding:10px 18px;border-radius:10px;background:linear-gradient(135deg,#0ea5e9,#14b8a6);color:#fff;text-decoration:none;font-weight:600;">查看详情</a>
  </div>
  <div style="padding:14px 22px;background:#f8fafc;color:#94a3b8;font-size:12px;border-top:1px solid #eef2f7;">本邮件由系统自动发出，请勿直接回复。</div>
</div></body></html>
HTML;
    }

    private static function smtpSend(string $host, int $port, string $scheme, string $user, string $pass, string $from, array $to, string $subject, string $html): bool
    {
        $timeout = 15;
        $ctx = $scheme === 'ssl'
            ? stream_context_create(['ssl' => ['verify_peer' => false, 'verify_peer_name' => false]])
            : null;
        $prefix = $scheme === 'ssl' ? 'ssl://' : '';
        $fp = @stream_socket_client($prefix . $host . ':' . $port, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $ctx);
        if (!$fp) return false;

        $talk = function ($cmd = null) use ($fp) {
            if ($cmd !== null) fwrite($fp, $cmd . "\r\n");
            $res = '';
            while (($line = fgets($fp, 600)) !== false) {
                $res .= $line;
                if (isset($line[3]) && $line[3] === ' ') break; // 单行响应（响应码后的第4个字符是空格）
                if ($line === '') break;
            }
            return $res;
        };

        $talk(null); // 欢迎语
        $talk('EHLO ' . (gethostname() ?: 'localhost'));
        if ($scheme === 'tls' || $port === 587 || $port === 25) {
            $r = $talk('STARTTLS');
            if (strpos($r, '220') === 0) {
                if (!@stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { fclose($fp); return false; }
                $talk('EHLO ' . (gethostname() ?: 'localhost'));
            }
        }
        if ($user !== '') {
            $talk('AUTH LOGIN');
            $talk(base64_encode($user));
            $talk(base64_encode($pass));
        }
        $talk('MAIL FROM:<' . $from . '>');
        foreach ($to as $t) $talk('RCPT TO:<' . $t . '>');
        $talk('DATA');
        $headers  = "From: {$from}\r\n";
        $headers .= "To: " . implode(', ', $to) . "\r\n";
        $headers .= "Subject: =?UTF-8?B?" . base64_encode($subject) . "?=\r\n";
        $headers .= "MIME-Version: 1.0\r\n";
        $headers .= "Content-Type: text/html; charset=UTF-8\r\n";
        $talk($headers . "\r\n" . $html . "\r\n.");
        $talk('QUIT');
        fclose($fp);
        return true;
    }

    /* ============== 企业微信（群机器人 webhook） ============== */

    private static function sendWeChat(string $title, string $body, string $url, array $mention): bool
    {
        $webhook = trim((string) (new Setting())->get('notify_wechat_webhook', ''));
        if ($webhook === '') return false;
        $content = "**{$title}**\n> " . str_replace("\n", "\n> ", $body);
        if ($url) $content .= "\n\n[查看详情](" . App::url($url) . ")";
        $payload = ['msgtype' => 'markdown', 'markdown' => ['content' => $content]];
        if ($mention) $payload['markdown']['mentioned_mobile_list'] = array_values($mention);
        return self::httpPostJson($webhook, $payload);
    }

    private static function httpPostJson(string $url, array $payload): bool
    {
        $json = json_encode($payload, JSON_UNESCAPED_UNICODE);
        if (function_exists('curl_init')) {
            $ch = curl_init($url);
            curl_setopt_array($ch, [
                CURLOPT_POST           => true,
                CURLOPT_HTTPHEADER     => ['Content-Type: application/json; charset=utf-8'],
                CURLOPT_POSTFIELDS     => $json,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_TIMEOUT        => 10,
                CURLOPT_SSL_VERIFYPEER => false,
                CURLOPT_SSL_VERIFYHOST => 0,
            ]);
            $res = curl_exec($ch);
            curl_close($ch);
            return $res !== false;
        }
        $ctx = stream_context_create([
            'http' => [
                'method'  => 'POST',
                'header'  => "Content-Type: application/json; charset=utf-8\r\n",
                'content' => $json,
                'timeout' => 10,
            ],
        ]);
        $res = @file_get_contents($url, false, $ctx);
        return $res !== false;
    }

    /* ============== 事件表（按需创建，兼容 MySQL / json 两种存储） ============== */

    private static function ensureTable(): void
    {
        if (Db::driver() !== 'mysql') return; // json 模式由 Model 自动建文件
        $sql = "CREATE TABLE IF NOT EXISTS `psi_events` (
            `id`         INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
            `sys`        VARCHAR(20)  NOT NULL DEFAULT 'psi',
            `type`       VARCHAR(40)  NOT NULL DEFAULT '',
            `level`      VARCHAR(20)  NOT NULL DEFAULT 'urgent',
            `title`      VARCHAR(255) NOT NULL DEFAULT '',
            `body`       TEXT,
            `url`        VARCHAR(255) NOT NULL DEFAULT '',
            `ref_no`     VARCHAR(64)  NOT NULL DEFAULT '',
            `recipients` TEXT,
            `channels`   VARCHAR(255) NOT NULL DEFAULT '[\"inapp\"]',
            `read_by`    TEXT,
            `created_at` DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
        Db::query($sql);
    }
}
