<?php

namespace App\Protocols;

use App\Models\Server;
use App\Utils\Helper;
use Illuminate\Support\Arr;
use App\Support\AbstractProtocol;

class General extends AbstractProtocol
{
    public $flags = ['general', 'v2rayn', 'v2rayng', 'passwall', 'ssrplus', 'sagernet'];

    public $allowedProtocols = [
        Server::TYPE_VMESS,
        Server::TYPE_VLESS,
        Server::TYPE_SHADOWSOCKS,
        Server::TYPE_TROJAN,
        Server::TYPE_HYSTERIA,
        Server::TYPE_SOCKS,
    ];

    protected $protocolRequirements = [
        'v2rayng.hysteria.protocol_settings.version' => [2 => '1.9.5'],
        'v2rayn.hysteria.protocol_settings.version' => [2 => '6.31'],
    ];

    public function handle()
    {
        $servers = $this->servers;
        $user = $this->user;
        $uri = '';

        foreach ($servers as $item) {
            $uri .= match ($item['type']) {
                Server::TYPE_VMESS => self::buildVmess($item['password'], $item),
                Server::TYPE_VLESS => self::buildVless($item['password'], $item),
                Server::TYPE_SHADOWSOCKS => self::buildShadowsocks($item['password'], $item),
                Server::TYPE_TROJAN => self::buildTrojan($item['password'], $item),
                Server::TYPE_HYSTERIA => self::buildHysteria($item['password'], $item),
                Server::TYPE_SOCKS => self::buildSocks($item['password'], $item),
                default => '',
            };
        }
        return response(base64_encode($uri))->header('content-type', 'text/plain');
    }

    public static function buildShadowsocks($password, $server)
    {
        $protocol_settings = $server['protocol_settings'];
        $name = rawurlencode($server['name']);
        $password = data_get($server, 'password', $password);
        $str = str_replace(
            ['+', '/', '='],
            ['-', '_', ''],
            base64_encode("{$protocol_settings['cipher']}:{$password}")
        );
        $addr = Helper::wrapIPv6($server['host']);
        $plugin = data_get($protocol_settings, 'plugin');
        $plugin_opts = data_get($protocol_settings, 'plugin_opts');
        $url = "ss://{$str}@{$addr}:{$server['port']}";
        if ($plugin && $plugin_opts) {
            $url .= '/?' . 'plugin=' . $plugin . ';' . rawurlencode($plugin_opts);
        }
        $url .= "#{$name}\r\n";
        return $url;
    }

    public static function buildVmess($uuid, $server)
    {
        $protocol_settings = $server['protocol_settings'];
        $config = [
            "v" => "2",
            "ps" => $server['name'],
            "add" => $server['host'],
            "port" => (string) $server['port'],
            "id" => $uuid,
            "aid" => '0',
            "net" => $server['protocol_settings']['network'],
            "type" => "none",
            "host" => "",
            "path" => "",
            "tls" => $protocol_settings['tls'] ? "tls" : "",
        ];
        if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
            $config['sni'] = $serverName;
        }

        switch ($protocol_settings['network']) {
            case 'tcp':
                if (data_get($protocol_settings, 'network_settings.header.type', 'none') !== 'none') {
                    $config['type'] = data_get($protocol_settings, 'network_settings.header.type', 'http');
                    $config['path'] = Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/']));
                    $config['host'] =
                        data_get($protocol_settings, 'network_settings.header.request.headers.Host')
                        ? Arr::random(data_get($protocol_settings, 'network_settings.header.request.headers.Host', ['/']), )
                        : null;
                }
                break;
            case 'ws':
                $config['type'] = 'ws';
                if ($path = data_get($protocol_settings, 'network_settings.path'))
                    $config['path'] = $path;
                if ($host = data_get($protocol_settings, 'network_settings.headers.Host'))
                    $config['host'] = $host;
                break;
            case 'grpc':
                $config['type'] = 'grpc';
                if ($path = data_get($protocol_settings, 'network_settings.serviceName'))
                    $config['path'] = $path;
                break;
            default:
                break;
        }
        return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
    }

    public static function buildVless($uuid, $server)
    {
        $protocol_settings = $server['protocol_settings'];
        $host = $server['host']; //节点地址
        $port = $server['port']; //节点端口
        $name = $server['name']; //节点名称

        $config = [
            'mode' => 'multi', //grpc传输模式
            'security' => '', //传输层安全 tls/reality
            'encryption' => 'none', //加密方式
            'type' => $server['protocol_settings']['network'], //传输协议
            'flow' => $protocol_settings['flow'] ? $protocol_settings['flow'] : null,
        ];
        // 处理TLS
        switch ($server['protocol_settings']['tls']) {
            case 1:
                $config['security'] = "tls";
                if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
                    $config['sni'] = $serverName;
                }
                break;
            case 2: //reality
                $config['security'] = "reality";
                $config['pbk'] = data_get($protocol_settings, 'reality_settings.public_key');
                $config['sid'] = data_get($protocol_settings, 'reality_settings.short_id');
                $config['sni'] = data_get($protocol_settings, 'reality_settings.server_name');
                $config['servername'] = data_get($protocol_settings, 'reality_settings.server_name');
                $config['spx'] = "/";
                $config['fp'] = Helper::getRandFingerprint();
                break;
            default:
                break;
        }
        // 处理传输协议
        switch ($server['protocol_settings']['network']) {
            case 'ws':
                if ($path = data_get($protocol_settings, 'network_settings.path'))
                    $config['path'] = $path;
                if ($wsHost = data_get($protocol_settings, 'network_settings.headers.Host'))
                    $config['host'] = $wsHost;
                break;
            case 'grpc':
                if ($path = data_get($protocol_settings, 'network_settings.serviceName'))
                    $config['serviceName'] = $path;
                break;
            case 'kcp':
                if ($path = data_get($protocol_settings, 'network_settings.seed'))
                    $config['path'] = $path;
                $config['type'] = data_get($protocol_settings, 'network_settings.header.type', 'none');
                break;
            case 'httpupgrade':
                if ($path = data_get($protocol_settings, 'network_settings.path'))
                    $config['path'] = $path;
                $config['host'] = data_get($protocol_settings, 'network_settings.host', $server['host']);
                break;
            case 'xhttp':
                $config['path'] = data_get($protocol_settings, 'network_settings.path');
                $config['host'] = data_get($protocol_settings, 'network_settings.host', $server['host']);
                $config['mode'] = data_get($protocol_settings, 'network_settings.mode', 'auto');
                $config['extra'] = json_encode(data_get($protocol_settings, 'network_settings.extra'));
                break;
        }

        $user = $uuid . '@' . Helper::wrapIPv6($host) . ':' . $port;
        $query = http_build_query($config);
        $fragment = urlencode($name);
        $link = sprintf("vless://%s?%s#%s\r\n", $user, $query, $fragment);
        return $link;
    }

    public static function buildTrojan($password, $server)
    {
        $protocol_settings = $server['protocol_settings'];
        $name = rawurlencode($server['name']);
        $array = [];
        $array['allowInsecure'] = $protocol_settings['allow_insecure'];
        if ($serverName = data_get($protocol_settings, 'server_name')) {
            $array['peer'] = $serverName;
            $array['sni'] = $serverName;
        }
        switch ($server['protocol_settings']['network']) {
            case 'ws':
                $array['type'] = 'ws';
                if ($path = data_get($protocol_settings, 'network_settings.path'))
                    $array['path'] = $path;
                if ($host = data_get($protocol_settings, 'network_settings.headers.Host'))
                    $array['host'] = $host;
                break;
            case 'grpc':
                // Follow V2rayN family standards
                $array['type'] = 'grpc';
                if ($serviceName = data_get($protocol_settings, 'network_settings.serviceName'))
                    $array['serviceName'] = $serviceName;
                break;
            default:
                break;
        }
        $query = http_build_query($array);
        $addr = Helper::wrapIPv6($server['host']);

        $uri = "trojan://{$password}@{$addr}:{$server['port']}?{$query}#{$name}";
        $uri .= "\r\n";
        return $uri;
    }

    public static function buildHysteria($password, $server)
    {
        $protocol_settings = $server['protocol_settings'];
        $params = [];
        // Return empty if version is not 2
        if ($server['protocol_settings']['version'] !== 2) {
            return '';
        }

        if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
            $params['sni'] = $serverName;
            $params['security'] = 'tls';
        }

        if (data_get($protocol_settings, 'obfs.open')) {
            $params['obfs'] = 'salamander';
            $params['obfs-password'] = data_get($protocol_settings, 'obfs.password');
        }
        if (isset($server['ports'])) {
            $params['mport'] = $server['ports'];
        }

        $params['insecure'] = data_get($protocol_settings, 'tls.allow_insecure');

        $query = http_build_query($params);
        $name = rawurlencode($server['name']);
        $addr = Helper::wrapIPv6($server['host']);

        $uri = "hysteria2://{$password}@{$addr}:{$server['port']}?{$query}#{$name}";
        $uri .= "\r\n";

        return $uri;
    }

    public static function buildSocks($password, $server)
    {
        $name = rawurlencode($server['name']);
        $credentials = base64_encode("{$password}:{$password}");
        return "socks://{$credentials}@{$server['host']}:{$server['port']}#{$name}\r\n";
    }
}
