using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime.ConstrainedExecution;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using DnsClient;

//////////////////////////////////

#region __________ INIT __________

using Microsoft.Extensions.Logging.Console;

var builder = WebApplication.CreateBuilder(args);

//控制台日志格式
builder.Services.AddLogging(opt =>
{
    opt.AddSimpleConsole(p =>
    {
        p.TimestampFormat = "[dd HH:mm:ss] ";
        p.SingleLine = true;
        p.ColorBehavior = LoggerColorBehavior.Enabled;
    });
});

using var host = builder.Build();
builder.WebHost.UseUrls("http://*:0");

await host.StartAsync();
var isRunning = true;
var cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, _) =>
{
    isRunning = false;
    cts.Cancel(false);
};

var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Hello, World!");

//Main
try
{
    await RealMain();
}
catch (Exception ex)
{
    logger.LogError(ex, "Main");
}
finally
{
    logger.LogInformation("Bye!");

    await host.StopAsync();

    Console.WriteLine();
    Console.Write("Press ENTER to exit...");
    Console.ReadLine();
}

#endregion __________ INIT __________

//////////////////////////////////
async Task RealMain()
{
    const string url = "http://example.com/index.html";
    const string host = "example.com";
    const string path = "/index.html";

    const string dnsServerName = "reliable-dns-server-in-hosts";
    var dnsServerIp = Dns.GetHostEntry(dnsServerName).AddressList.FirstOrDefault();
    var lookup = new LookupClient(dnsServerIp);

    var result = await lookup.QueryAsync(host, QueryType.A);

    var record = result.Answers.ARecords().FirstOrDefault();
    var ip = record?.Address;
    var tcpClient = new TcpClient();
    tcpClient.Connect(new IPEndPoint(ip, 443)); // stuck if was ip gfw-ed

    var ssl = new SslStream(tcpClient.GetStream());
    var sslOptions = new SslClientAuthenticationOptions
    {
        TargetHost = string.Empty, // Leave this empty to avoid sending SNI
        RemoteCertificateValidationCallback = (_, certificate, chain, errs) =>
        {
            if (errs == SslPolicyErrors.None) return true;

            if (errs != SslPolicyErrors.RemoteCertificateNameMismatch) return false;

            if (certificate is not X509Certificate2 cert2) return false;

            // 先验证证书的有效期
            if (DateTime.Now < cert2.NotBefore || DateTime.Now > cert2.NotAfter) return false;

            // 然后比较证书名称和主机名称 (任意一个匹配)
            var names = GetAllSubjectAlternativeNames(cert2);
            var flagNameMatched = false;
            foreach (var certName in names)
            {
                if (certName.StartsWith("*."))
                {
                    if (!host.EndsWith(certName[2..], StringComparison.OrdinalIgnoreCase)) continue;
                    flagNameMatched = true;
                    break;
                }

                if (certName.Equals(host, StringComparison.OrdinalIgnoreCase))
                {
                    flagNameMatched = true;
                    break;
                }
            }

            if (flagNameMatched == false) return false;

            // 最后检查信任链
            if (chain == null) return false;

            chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; //检测吊销耗时太长,忽略
            chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
            chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 10);
            chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;

            var isValidChain = chain.Build(cert2);
            if (isValidChain) return true;

            foreach (X509ChainStatus chainStatus in chain.ChainStatus)
            {
                // 仅处理会影响安全性的错误状态
                if (chainStatus.Status == X509ChainStatusFlags.RevocationStatusUnknown ||
                    chainStatus.Status == X509ChainStatusFlags.OfflineRevocation ||
                    chainStatus.Status == X509ChainStatusFlags.NoError)
                {
                    continue;
                }

                // 其他任何错误状态都认为证书无效
                return false;
            }

            return true;
        }
    };

    await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token);

    ssl.Write(Encoding.ASCII.GetBytes($"GET {path} HTTP/1.1\r\n"));
    ssl.Write(Encoding.ASCII.GetBytes($"Host: {host}\r\n"));
    ssl.Write(Encoding.ASCII.GetBytes($"\r\n"));

    var reader = new StreamReader(ssl);

    var lines = new List<string>(); // <- HTTP/1.1 200 OK -- PoC SUCC!
    do
    {
        var line = reader.ReadLine();
        if (line == "") break;
        lines.Add(line);
    } while (true);

    int bp = 0;
}

IReadOnlyList<string> GetAllSubjectAlternativeNames(X509Certificate2 cert)
{
    var names = new HashSet<string>();

    foreach (var extension in cert.Extensions)
    {
        if (extension is X509SubjectAlternativeNameExtension sanExtension)
        {
            foreach (var name in sanExtension.EnumerateDnsNames()) names.Add(name);
        }
        else if (extension.Oid?.Value == "2.5.29.17") // Subject Alternative Name OID
        {
            var asnData = new AsnEncodedData(extension.Oid, extension.RawData);
            var sanString = asnData.Format(true);

            var sanParts = sanString.Split(new[] { ", ", "DNS Name=", " " }, StringSplitOptions.RemoveEmptyEntries);
            foreach (var part in sanParts)
            {
                if (!string.IsNullOrEmpty(part) && !part.StartsWith("IPAddress") && !part.StartsWith("Uri"))
                {
                    names.Add(part);
                }
            }
        }
    }

    // 添加证书的CN(通用名称)
    var certName = cert.GetNameInfo(X509NameType.DnsName, false);
    if (!string.IsNullOrEmpty(certName)) names.Add(certName);

    return [.. names];
}