package proxy

import (
	"bufio"
	"crypto/tls"
	"encoding/base64"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"strings"

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

// HTTPConnectDial connects using HTTP CONNECT via proxyDialer
//
// It will read the HTTP proxy address from the environment in the
// standard way.
//
// It optionally takes a proxyDialer to dial the HTTP proxy server.
// If nil is passed, it will use the default net.Dialer.
func HTTPConnectDial(network, addr string, proxyURL *url.URL, proxyDialer proxy.Dialer) (net.Conn, error) {
	if proxyDialer == nil {
		proxyDialer = &net.Dialer{}
	}
	if proxyURL == nil {
		return proxyDialer.Dial(network, addr)
	}

	// prepare proxy host with default ports
	host := proxyURL.Host
	if !strings.Contains(host, ":") {
		if strings.EqualFold(proxyURL.Scheme, "https") {
			host += ":443"
		} else {
			host += ":80"
		}
	}

	// connect to proxy
	conn, err := proxyDialer.Dial(network, host)
	if err != nil {
		return nil, fmt.Errorf("HTTP CONNECT proxy failed to Dial: %q", err)
	}

	// wrap TLS if HTTPS proxy
	if strings.EqualFold(proxyURL.Scheme, "https") {
		tlsConfig := &tls.Config{ServerName: proxyURL.Hostname()}
		tlsConn := tls.Client(conn, tlsConfig)
		if err := tlsConn.Handshake(); err != nil {
			_ = conn.Close()
			return nil, fmt.Errorf("HTTP CONNECT proxy failed to make TLS connection: %q", err)
		}
		conn = tlsConn
	}

	// send CONNECT
	user := proxyURL.User
	if user != nil {
		credential := base64.StdEncoding.EncodeToString([]byte(user.String()))
		_, err = fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\nProxy-Authorization: Basic %s\r\n\r\n", addr, addr, credential)
	} else {
		_, err = fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", addr, addr)
	}
	if err != nil {
		_ = conn.Close()
		return nil, fmt.Errorf("HTTP CONNECT proxy failed to send CONNECT: %q", err)
	}
	br := bufio.NewReader(conn)
	req := &http.Request{URL: &url.URL{Scheme: "http", Host: addr}}
	resp, err := http.ReadResponse(br, req)
	if err != nil {
		_ = conn.Close()
		return nil, fmt.Errorf("HTTP CONNECT proxy failed to read response: %q", err)
	}
	if resp.StatusCode != http.StatusOK {
		_ = conn.Close()
		return nil, fmt.Errorf("HTTP CONNECT proxy failed: %s", resp.Status)
	}
	return conn, nil
}
