package httpclient

import (
	"context"
	stdTLS "crypto/tls"
	"io"
	"net"
	"net/http"
	"strings"

	"github.com/sagernet/sing-box/common/tls"
	E "github.com/sagernet/sing/common/exceptions"
	M "github.com/sagernet/sing/common/metadata"
	N "github.com/sagernet/sing/common/network"

	"golang.org/x/net/idna"
)

func dialTLS(ctx context.Context, rawDialer N.Dialer, baseTLSConfig tls.Config, destination M.Socksaddr, nextProtos []string, expectProto string) (net.Conn, error) {
	if baseTLSConfig == nil {
		return nil, E.New("TLS transport unavailable")
	}
	tlsConfig := baseTLSConfig.Clone()
	if tlsConfig.ServerName() == "" && destination.IsValid() {
		tlsConfig.SetServerName(destination.AddrString())
	}
	tlsConfig.SetNextProtos(nextProtos)
	conn, err := rawDialer.DialContext(ctx, N.NetworkTCP, destination)
	if err != nil {
		return nil, err
	}
	tlsConn, err := tls.ClientHandshake(ctx, conn, tlsConfig)
	if err != nil {
		conn.Close()
		return nil, err
	}
	if expectProto != "" && tlsConn.ConnectionState().NegotiatedProtocol != expectProto {
		tlsConn.Close()
		return nil, errHTTP2Fallback
	}
	return tlsConn, nil
}

func applyHeaders(request *http.Request, headers http.Header, host string) {
	for header, values := range headers {
		request.Header[header] = append([]string(nil), values...)
	}
	if host != "" {
		request.Host = host
	}
}

func requestRequiresHTTP1(request *http.Request) bool {
	return strings.Contains(strings.ToLower(request.Header.Get("Connection")), "upgrade") &&
		strings.EqualFold(request.Header.Get("Upgrade"), "websocket")
}

func requestReplayable(request *http.Request) bool {
	return request.Body == nil || request.Body == http.NoBody || request.GetBody != nil
}

func cloneRequestForRetry(request *http.Request) *http.Request {
	cloned := request.Clone(request.Context())
	if request.Body != nil && request.Body != http.NoBody && request.GetBody != nil {
		cloned.Body = mustGetBody(request)
	}
	return cloned
}

func mustGetBody(request *http.Request) io.ReadCloser {
	body, err := request.GetBody()
	if err != nil {
		panic(err)
	}
	return body
}

func requestAuthority(request *http.Request) string {
	if request == nil || request.URL == nil || request.URL.Host == "" {
		return ""
	}
	host, port, err := net.SplitHostPort(request.URL.Host)
	if err != nil {
		host = request.URL.Host
		port = ""
	}
	if port == "" {
		if request.URL.Scheme == "http" {
			port = "80"
		} else {
			port = "443"
		}
	}
	if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
		return host + ":" + port
	}
	ascii, idnaErr := idna.Lookup.ToASCII(host)
	if idnaErr == nil {
		host = ascii
	} else {
		host = strings.ToLower(host)
	}
	return net.JoinHostPort(host, port)
}

func buildSTDTLSConfig(baseTLSConfig tls.Config, destination M.Socksaddr, nextProtos []string) (*stdTLS.Config, error) {
	if baseTLSConfig == nil {
		return nil, nil
	}
	tlsConfig := baseTLSConfig.Clone()
	if tlsConfig.ServerName() == "" && destination.IsValid() {
		tlsConfig.SetServerName(destination.AddrString())
	}
	tlsConfig.SetNextProtos(nextProtos)
	return tlsConfig.STDConfig()
}
