package http

import (
	"context"
	"crypto/tls"
	"net"
	"net/http"
	"strings"
	"time"

	"github.com/Dreamacro/clash/adapter/inbound"
	N "github.com/Dreamacro/clash/common/net"
	C "github.com/Dreamacro/clash/constant"
	"github.com/Dreamacro/clash/transport/socks5"
)

func isUpgradeRequest(req *http.Request) bool {
	return strings.EqualFold(req.Header.Get("Connection"), "Upgrade")
}

func handleUpgrade(localConn net.Conn, source net.Addr, request *http.Request, in chan<- C.ConnContext) (resp *http.Response) {
	removeProxyHeaders(request.Header)
	removeExtraHTTPHostPort(request)

	address := request.Host
	if _, _, err := net.SplitHostPort(address); err != nil {
		port := "80"
		if request.TLS != nil {
			port = "443"
		}
		address = net.JoinHostPort(address, port)
	}

	dstAddr := socks5.ParseAddr(address)
	if dstAddr == nil {
		return
	}

	left, right := net.Pipe()

	in <- inbound.NewHTTP(dstAddr, source, right)

	var remoteServer *N.BufferedConn
	if request.TLS != nil {
		tlsConn := tls.Client(left, &tls.Config{
			ServerName: request.URL.Hostname(),
		})

		ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
		defer cancel()
		if tlsConn.HandshakeContext(ctx) != nil {
			_ = localConn.Close()
			_ = left.Close()
			return
		}

		remoteServer = N.NewBufferedConn(tlsConn)
	} else {
		remoteServer = N.NewBufferedConn(left)
	}
	defer func() {
		_ = remoteServer.Close()
	}()

	err := request.Write(remoteServer)
	if err != nil {
		_ = localConn.Close()
		return
	}

	resp, err = http.ReadResponse(remoteServer.Reader(), request)
	if err != nil {
		_ = localConn.Close()
		return
	}

	if resp.StatusCode == http.StatusSwitchingProtocols {
		removeProxyHeaders(resp.Header)

		err = localConn.SetReadDeadline(time.Time{}) // set to not time out
		if err != nil {
			return
		}

		err = resp.Write(localConn)
		if err != nil {
			return
		}

		N.Relay(remoteServer, localConn) // blocking here
		_ = localConn.Close()
		resp = nil
	}
	return
}
