namespace ServiceLib.Handler.Fmt;

public class ShadowsocksFmt : BaseFmt
{
    public static ProfileItem? Resolve(string str, out string msg)
    {
        msg = ResUI.ConfigurationFormatIncorrect;
        ProfileItem? item;

        item = ResolveSSLegacy(str) ?? ResolveSip002(str);
        if (item == null)
        {
            return null;
        }

        if (item.Address.Length == 0 || item.Port == 0 || item.GetProtocolExtra().SsMethod.IsNullOrEmpty() || item.Password.Length == 0)
        {
            return null;
        }

        item.ConfigType = EConfigType.Shadowsocks;

        return item;
    }

    public static string? ToUri(ProfileItem? item)
    {
        if (item == null)
        {
            return null;
        }
        var remark = string.Empty;
        if (item.Remarks.IsNotEmpty())
        {
            remark = "#" + Utils.UrlEncode(item.Remarks);
        }
        //url = string.Format("{0}:{1}@{2}:{3}",
        //    item.security,
        //    item.id,
        //    item.address,
        //    item.port);
        //url = Utile.Base64Encode(url);
        //new Sip002
        var pw = Utils.Base64Encode($"{item.GetProtocolExtra().SsMethod}:{item.Password}", true);

        // plugin
        var plugin = string.Empty;
        var pluginArgs = string.Empty;

        if (item.Network == nameof(ETransport.tcp) && item.HeaderType == Global.TcpHeaderHttp)
        {
            plugin = "obfs-local";
            pluginArgs = $"obfs=http;obfs-host={item.RequestHost};";
        }
        else
        {
            if (item.Network == nameof(ETransport.ws))
            {
                pluginArgs += "mode=websocket;";
                pluginArgs += $"host={item.RequestHost};";
                // https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
                // Equal signs and commas [and backslashes] must be escaped with a backslash.
                var path = item.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
                pluginArgs += $"path={path};";
            }
            else if (item.Network == nameof(ETransport.quic))
            {
                pluginArgs += "mode=quic;";
            }
            if (item.StreamSecurity == Global.StreamSecurity)
            {
                pluginArgs += "tls;";
                var certs = CertPemManager.ParsePemChain(item.Cert);
                if (certs.Count > 0)
                {
                    var cert = certs.First();
                    const string beginMarker = "-----BEGIN CERTIFICATE-----\n";
                    const string endMarker = "\n-----END CERTIFICATE-----";

                    var base64Content = cert.Replace(beginMarker, "").Replace(endMarker, "").Trim();

                    base64Content = base64Content.Replace("=", "\\=");

                    pluginArgs += $"certRaw={base64Content};";
                }
            }
            if (pluginArgs.Length > 0)
            {
                plugin = "v2ray-plugin";
                pluginArgs += "mux=0;";
            }
        }

        var dicQuery = new Dictionary<string, string>();
        if (plugin.IsNotEmpty())
        {
            var pluginStr = plugin + ";" + pluginArgs;
            // pluginStr remove last ';' and url encode
            if (pluginStr.EndsWith(';'))
            {
                pluginStr = pluginStr[..^1];
            }
            dicQuery["plugin"] = Utils.UrlEncode(pluginStr);
        }

        return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, dicQuery, remark);
    }

    private static readonly Regex UrlFinder = new(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
    private static readonly Regex DetailsParser = new(@"^((?<method>.+?):(?<password>.*)@(?<hostname>.+?):(?<port>\d+?))$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

    private static ProfileItem? ResolveSSLegacy(string result)
    {
        var match = UrlFinder.Match(result);
        if (!match.Success)
        {
            return null;
        }

        ProfileItem item = new();
        var base64 = match.Groups["base64"].Value.TrimEnd('/');
        var tag = match.Groups["tag"].Value;
        if (tag.IsNotEmpty())
        {
            item.Remarks = Utils.UrlDecode(tag);
        }
        Match details;
        try
        {
            details = DetailsParser.Match(Utils.Base64Decode(base64));
        }
        catch (FormatException)
        {
            return null;
        }
        if (!details.Success)
        {
            return null;
        }
        item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = details.Groups["method"].Value });
        item.Password = details.Groups["password"].Value;
        item.Address = details.Groups["hostname"].Value;
        item.Port = details.Groups["port"].Value.ToInt();
        return item;
    }

    private static ProfileItem? ResolveSip002(string result)
    {
        var parsedUrl = Utils.TryUri(result);
        if (parsedUrl == null)
        {
            return null;
        }

        ProfileItem item = new()
        {
            Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
            Address = parsedUrl.IdnHost,
            Port = parsedUrl.Port,
        };
        var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
        //2022-blake3
        if (rawUserInfo.Contains(':'))
        {
            var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2);
            if (userInfoParts.Length != 2)
            {
                return null;
            }
            item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = userInfoParts.First() });
            item.Password = Utils.UrlDecode(userInfoParts.Last());
        }
        else
        {
            // parse base64 UserInfo
            var userInfo = Utils.Base64Decode(rawUserInfo);
            var userInfoParts = userInfo.Split(new[] { ':' }, 2);
            if (userInfoParts.Length != 2)
            {
                return null;
            }
            item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = userInfoParts.First() });
            item.Password = userInfoParts.Last();
        }

        var queryParameters = Utils.ParseQueryString(parsedUrl.Query);
        if (queryParameters["plugin"] != null)
        {
            var pluginStr = queryParameters["plugin"];
            var pluginParts = pluginStr.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

            if (pluginParts.Length == 0)
            {
                return null;
            }

            var pluginName = pluginParts[0];

            // A typo in https://github.com/shadowsocks/shadowsocks-org/blob/6b1c064db4129de99c516294960e731934841c94/docs/doc/sip002.md?plain=1#L15
            // "simple-obfs" should be "obfs-local"
            if (pluginName == "simple-obfs")
            {
                pluginName = "obfs-local";
            }

            // Parse obfs-local plugin
            if (pluginName == "obfs-local")
            {
                var obfsMode = pluginParts.FirstOrDefault(t => t.StartsWith("obfs="));
                var obfsHost = pluginParts.FirstOrDefault(t => t.StartsWith("obfs-host="));

                if ((!obfsMode.IsNullOrEmpty()) && obfsMode.Contains("obfs=http") && obfsHost.IsNotEmpty())
                {
                    obfsHost = obfsHost.Replace("obfs-host=", "");
                    item.Network = Global.DefaultNetwork;
                    item.HeaderType = Global.TcpHeaderHttp;
                    item.RequestHost = obfsHost;
                }
            }
            // Parse v2ray-plugin
            else if (pluginName == "v2ray-plugin")
            {
                var mode = pluginParts.FirstOrDefault(t => t.StartsWith("mode="), "websocket");
                var host = pluginParts.FirstOrDefault(t => t.StartsWith("host="));
                var path = pluginParts.FirstOrDefault(t => t.StartsWith("path="));
                var hasTls = pluginParts.Any(t => t == "tls");
                var certRaw = pluginParts.FirstOrDefault(t => t.StartsWith("certRaw="));
                var mux = pluginParts.FirstOrDefault(t => t.StartsWith("mux="));

                var modeValue = mode.Replace("mode=", "");
                if (modeValue == "websocket")
                {
                    item.Network = nameof(ETransport.ws);
                    if (!host.IsNullOrEmpty())
                    {
                        item.RequestHost = host.Replace("host=", "");
                        item.Sni = item.RequestHost;
                    }
                    if (!path.IsNullOrEmpty())
                    {
                        var pathValue = path.Replace("path=", "");
                        pathValue = pathValue.Replace("\\=", "=").Replace("\\,", ",").Replace("\\\\", "\\");
                        item.Path = pathValue;
                    }
                }
                else if (modeValue == "quic")
                {
                    item.Network = nameof(ETransport.quic);
                }

                if (hasTls)
                {
                    item.StreamSecurity = Global.StreamSecurity;

                    if (!certRaw.IsNullOrEmpty())
                    {
                        var certBase64 = certRaw.Replace("certRaw=", "");

                        certBase64 = certBase64.Replace("\\=", "=");

                        const string beginMarker = "-----BEGIN CERTIFICATE-----\n";
                        const string endMarker = "\n-----END CERTIFICATE-----";
                        var certPem = beginMarker + certBase64 + endMarker;
                        item.Cert = certPem;
                    }
                }

                if (!mux.IsNullOrEmpty())
                {
                    var muxValue = mux.Replace("mux=", "");
                    var muxCount = muxValue.ToInt();
                    if (muxCount > 0)
                    {
                        return null;
                    }
                }
            }
        }
        return item;
    }

    public static List<ProfileItem>? ResolveSip008(string result)
    {
        //SsSIP008
        var lstSsServer = JsonUtils.Deserialize<List<SsServer>>(result);
        if (lstSsServer?.Count <= 0)
        {
            var ssSIP008 = JsonUtils.Deserialize<SsSIP008>(result);
            if (ssSIP008?.servers?.Count > 0)
            {
                lstSsServer = ssSIP008.servers;
            }
        }

        if (lstSsServer?.Count > 0)
        {
            List<ProfileItem> lst = [];
            foreach (var it in lstSsServer)
            {
                var ssItem = new ProfileItem()
                {
                    Remarks = it.remarks,
                    Password = it.password,
                    Address = it.server,
                    Port = it.server_port.ToInt()
                };
                ssItem.SetProtocolExtra(new ProtocolExtraItem() { SsMethod = it.method });
                lst.Add(ssItem);
            }
            return lst;
        }
        return null;
    }
}
