// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package utils

import (
	"context"
	"net"
	"strings"

	"crypto/tls"

	"github.com/gravitational/trace"
)

// DialWithContext dials with context
type DialWithContextFunc func(ctx context.Context, network, addr string) (net.Conn, error)

// TLSDial dials and establishes TLS connection using custom dialer
// is similar to tls.DialWithDialer
func TLSDial(ctx context.Context, dial DialWithContextFunc, network, addr string, tlsConfig *tls.Config) (*tls.Conn, error) {
	if tlsConfig == nil {
		tlsConfig = &tls.Config{}
	}

	plainConn, err := dial(ctx, network, addr)
	if err != nil {
		return nil, trace.Wrap(err)
	}

	colonPos := strings.LastIndex(addr, ":")
	if colonPos == -1 {
		colonPos = len(addr)
	}
	hostname := addr[:colonPos]

	// If no ServerName is set, infer the ServerName
	// from the hostname we're connecting to.
	if tlsConfig.ServerName == "" {
		// Make a copy to avoid polluting argument or default.
		c := tlsConfig.Clone()
		c.ServerName = hostname
		tlsConfig = c
	}

	conn := tls.Client(plainConn, tlsConfig)
	errC := make(chan error, 1)
	go func() {
		err := conn.Handshake()
		errC <- err
	}()

	select {
	case err := <-errC:
		if err != nil {
			plainConn.Close()
			return nil, trace.Wrap(err)
		}
	case <-ctx.Done():
		plainConn.Close()
		return nil, trace.BadParameter("tls handshake has been cancelled due to timeout")
	}

	if tlsConfig.InsecureSkipVerify {
		return conn, nil
	}

	if err := conn.VerifyHostname(tlsConfig.ServerName); err != nil {
		plainConn.Close()
		return nil, trace.Wrap(err)
	}

	return conn, nil
}
