package transport

import (
	"bufio"
	"crypto/tls"
	"net"
	"net/http"

	"go-micro.dev/v5/logger"
	maddr "go-micro.dev/v5/internal/util/addr"
	mnet "go-micro.dev/v5/internal/util/net"
	mls "go-micro.dev/v5/internal/util/tls"
)

type httpTransport struct {
	opts Options
}

func NewHTTPTransport(opts ...Option) *httpTransport {
	options := Options{
		BuffSizeH2: DefaultBufSizeH2,
		Logger:     logger.DefaultLogger,
	}

	for _, o := range opts {
		o(&options)
	}

	return &httpTransport{opts: options}
}

func (h *httpTransport) Init(opts ...Option) error {
	for _, o := range opts {
		o(&h.opts)
	}

	return nil
}

func (h *httpTransport) Dial(addr string, opts ...DialOption) (Client, error) {
	dopts := DialOptions{
		Timeout: DefaultDialTimeout,
	}

	for _, opt := range opts {
		opt(&dopts)
	}

	var (
		conn net.Conn
		err  error
	)

	if h.opts.Secure || h.opts.TLSConfig != nil {
		config := h.opts.TLSConfig
		if config == nil {
			config = &tls.Config{
				InsecureSkipVerify: dopts.InsecureSkipVerify,
			}
		}

		config.NextProtos = []string{"http/1.1"}

		conn, err = newConn(func(addr string) (net.Conn, error) {
			return tls.DialWithDialer(&net.Dialer{Timeout: dopts.Timeout}, "tcp", addr, config)
		})(addr)
	} else {
		conn, err = newConn(func(addr string) (net.Conn, error) {
			return net.DialTimeout("tcp", addr, dopts.Timeout)
		})(addr)
	}

	if err != nil {
		return nil, err
	}

	return &httpTransportClient{
		ht:       h,
		addr:     addr,
		conn:     conn,
		buff:     bufio.NewReader(conn),
		dialOpts: dopts,
		req:      make(chan *http.Request, 100),
		local:    conn.LocalAddr().String(),
		remote:   conn.RemoteAddr().String(),
	}, nil
}

func (h *httpTransport) Listen(addr string, opts ...ListenOption) (Listener, error) {
	var options ListenOptions
	for _, o := range opts {
		o(&options)
	}

	var (
		list net.Listener
		err  error
	)

	switch listener := getNetListener(&options); {
	// Extracted listener from context
	case listener != nil:
		getList := func(addr string) (net.Listener, error) {
			return listener, nil
		}

		list, err = mnet.Listen(addr, getList)

	// Needs to create self signed certificate
	case h.opts.Secure || h.opts.TLSConfig != nil:
		config := h.opts.TLSConfig

		getList := func(addr string) (net.Listener, error) {
			if config != nil {
				return tls.Listen("tcp", addr, config)
			}

			hosts := []string{addr}

			// check if its a valid host:port
			if host, _, err := net.SplitHostPort(addr); err == nil {
				if len(host) == 0 {
					hosts = maddr.IPs()
				} else {
					hosts = []string{host}
				}
			}

			// generate a certificate
			cert, err := mls.Certificate(hosts...)
			if err != nil {
				return nil, err
			}

			config = &tls.Config{
				Certificates: []tls.Certificate{cert},
				MinVersion:   tls.VersionTLS12,
			}

			return tls.Listen("tcp", addr, config)
		}

		list, err = mnet.Listen(addr, getList)

	// Create new basic net listener
	default:
		getList := func(addr string) (net.Listener, error) {
			return net.Listen("tcp", addr)
		}

		list, err = mnet.Listen(addr, getList)
	}

	if err != nil {
		return nil, err
	}

	return &httpTransportListener{
		ht:       h,
		listener: list,
	}, nil
}

func (h *httpTransport) Options() Options {
	return h.opts
}

func (h *httpTransport) String() string {
	return "http"
}
