<?php

namespace Kylin\Notification\Channels;

use GuzzleHttp\Client;
use Kylin\Notification\Contracts\ChannelInterface;
use Kylin\Notification\Models\Notification;

/**
 * Apple Push Notification service (APNs) 渠道
 * 基于 APNs HTTP/2 Provider API 直接调用，无需第三方 SDK
 * API文档：https://developer.apple.com/documentation/usernotifications/sending-notification-requests-to-apns
 */
class ApnsChannel implements ChannelInterface
{
    /**
     * APNs 生产环境地址
     */
    private const PRODUCTION_URL = 'https://api.push.apple.com';

    /**
     * APNs 沙盒环境地址
     */
    private const SANDBOX_URL = 'https://api.sandbox.push.apple.com';

    public function send(Notification $notification, array $params = []): array
    {
        try {
            $deviceToken = $params['device_token'] ?? $params['to'] ?? null;

            if (!$deviceToken) {
                return ['success' => false, 'message_id' => null, 'error' => 'Missing device_token', 'raw_response' => null];
            }

            $config = config('kylin-notification.channels.apns', []);
            $isProduction = $config['environment'] ?? 'production' === 'production';
            $baseUrl = $isProduction ? self::PRODUCTION_URL : self::SANDBOX_URL;

            // 构建 APNs 请求头
            $headers = $this->buildHeaders($config, $params);

            // 构建 APNs 请求体
            $payload = $this->buildPayload($notification, $params);

            // 发送请求
            $client = new Client(['version' => 2.0]);
            $response = $client->post("{$baseUrl}/3/device/{$deviceToken}", [
                'headers' => $headers,
                'json' => $payload,
            ]);

            $statusCode = $response->getStatusCode();
            $body = json_decode($response->getBody()->getContents(), true);
            $apnsId = $response->getHeaderLine('apns-id');

            if ($statusCode === 200) {
                return [
                    'success' => true,
                    'message_id' => $apnsId,
                    'error' => null,
                    'raw_response' => $body,
                ];
            }

            return [
                'success' => false,
                'message_id' => $apnsId ?: null,
                'error' => $body['reason'] ?? "HTTP {$statusCode}",
                'raw_response' => $body,
            ];
        } catch (\Throwable $e) {
            return ['success' => false, 'message_id' => null, 'error' => $e->getMessage(), 'raw_response' => null];
        }
    }

    public function getName(): string
    {
        return 'apns';
    }

    /**
     * 构建请求头
     * 支持两种认证方式：Token（JWT）和 Certificate
     */
    private function buildHeaders(array $config, array $params): array
    {
        $headers = [
            'content-type' => 'application/json',
        ];

        // 优先使用 Token 认证（推荐）
        $authType = $config['auth_type'] ?? 'token';

        if ($authType === 'token') {
            $headers['authorization'] = 'bearer ' . $this->generateJwt($config);
        } else {
            // Certificate 认证由 Guzzle 的 cert 选项处理
            // 此处仅设置 apns-topic
        }

        // apns-topic: Bundle ID
        $topic = $params['topic'] ?? $config['topic'] ?? '';
        if ($topic) {
            $headers['apns-topic'] = $topic;
        }

        // apns-push-type: alert/background/voip/complication/fileprovider/mdm
        $pushType = $params['push_type'] ?? 'alert';
        $headers['apns-push-type'] = $pushType;

        // apns-priority: 10=立即发送, 5=省电模式
        $priority = $params['priority'] ?? 10;
        $headers['apns-priority'] = (string) $priority;

        // apns-collapse-id: 合并相同ID的通知
        if (isset($params['collapse_id'])) {
            $headers['apns-collapse-id'] = $params['collapse_id'];
        }

        return $headers;
    }

    /**
     * 构建 APNs 请求体
     */
    private function buildPayload(Notification $notification, array $params): array
    {
        $aps = [];

        // 标准通知
        if (isset($params['alert'])) {
            $aps['alert'] = $params['alert'];
        } else {
            $aps['alert'] = [
                'title' => $params['title'] ?? $notification->title,
                'body' => $params['body'] ?? $notification->content,
            ];
        }

        // 徽章数
        if (isset($params['badge'])) {
            $aps['badge'] = (int) $params['badge'];
        }

        // 声音
        if (isset($params['sound'])) {
            $aps['sound'] = $params['sound'];
        }

        // 内容可用（静默推送）
        if (isset($params['content-available']) && $params['content-available']) {
            $aps['content-available'] = 1;
        }

        // 可变内容（富媒体推送）
        if (isset($params['mutable-content']) && $params['mutable-content']) {
            $aps['mutable-content'] = 1;
        }

        // 分类（交互通知）
        if (isset($params['category'])) {
            $aps['category'] = $params['category'];
        }

        // 线程ID（通知分组）
        if (isset($params['thread-id'])) {
            $aps['thread-id'] = $params['thread-id'];
        }

        $payload = ['aps' => $aps];

        // 自定义数据
        if (isset($params['custom_data'])) {
            $payload = array_merge($payload, $params['custom_data']);
        }

        return $payload;
    }

    /**
     * 生成 JWT Token（Token-Based Authentication）
     * 使用 p8 密钥文件签名
     */
    private function generateJwt(array $config): string
    {
        $keyId = $config['key_id'] ?? '';
        $teamId = $config['team_id'] ?? '';
        $privateKey = $config['private_key'] ?? '';

        // 从文件读取或直接使用
        if (empty($privateKey) && !empty($config['key_path'])) {
            $privateKey = file_get_contents($config['key_path']);
        }

        if (!$privateKey || !$keyId || !$teamId) {
            throw new \RuntimeException('APNs Token auth requires key_id, team_id, and private_key/key_path');
        }

        $header = $this->base64UrlEncode(json_encode([
            'alg' => 'ES256',
            'kid' => $keyId,
        ]));

        $now = time();
        $claims = $this->base64UrlEncode(json_encode([
            'iss' => $teamId,
            'iat' => $now,
            'exp' => $now + 3600, // 1小时有效期
        ]));

        $signatureInput = "{$header}.{$claims}";

        openssl_sign($signatureInput, $signature, $privateKey, OPENSSL_ALGO_SHA256);

        $encodedSignature = $this->base64UrlEncode($signature);

        return "{$header}.{$claims}.{$encodedSignature}";
    }

    private function base64UrlEncode(string $data): string
    {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }
}
