package surf

import (
	"errors"
	"fmt"
	"math/rand"
	"net/textproto"

	"github.com/enetx/g"
	"github.com/enetx/http/httptrace"
	"github.com/enetx/surf/header"
)

// defaultUserAgentMW sets the default User-Agent header for surf requests.
// Only sets the header if no User-Agent is already present in the request.
// Uses the predefined _userAgent constant as the default value.
func defaultUserAgentMW(req *Request) error {
	if headers := req.GetRequest().Header; headers.Get(header.USER_AGENT) == "" {
		// Set the default user-agent header.
		headers.Set(header.USER_AGENT, _userAgent)
	}

	return nil
}

// userAgentMW configures a custom User-Agent header for HTTP requests.
// Supports various input types for flexibility:
// - string or g.String: Uses the value directly
// - []string or g.Slice[string]: Randomly selects from the slice (useful for rotation)
// - g.Slice[g.String]: Randomly selects from g.String slice
// Returns an error for unsupported types or empty slices.
func userAgentMW(req *Request, userAgent any) error {
	var ua string

	switch v := userAgent.(type) {
	case string:
		ua = v
	case g.String:
		ua = v.Std()
	case []string:
		if len(v) == 0 {
			return &ErrUserAgentType{"cannot select a random user agent from an empty slice"}
		}
		ua = v[rand.Intn(len(v))]
	case g.Slice[string]:
		if v.Empty() {
			return &ErrUserAgentType{"cannot select a random user agent from an empty slice"}
		}
		ua = v.Random()
	case g.Slice[g.String]:
		if v.Empty() {
			return &ErrUserAgentType{"cannot select a random user agent from an empty slice"}
		}
		ua = v.Random().Std()
	default:
		return &ErrUserAgentType{fmt.Sprintf("'%T' %v", v, v)}
	}

	req.GetRequest().Header.Set(header.USER_AGENT, ua)

	return nil
}

// got101ResponseMW configures request tracing to handle HTTP 101 Switching Protocols responses.
// Sets up client trace callbacks to detect and handle protocol switching responses.
// Returns an error specifically for HTTP 101 responses to allow special handling of protocol upgrades.
// Other 1xx responses are ignored and allowed to proceed normally.
func got101ResponseMW(req *Request) error {
	req.WithContext(httptrace.WithClientTrace(req.GetRequest().Context(),
		&httptrace.ClientTrace{
			Got1xxResponse: func(code int, _ textproto.MIMEHeader) error {
				if code != 101 {
					return nil
				}

				return &Err101ResponseCode{
					fmt.Sprintf(`%s "%s" error:`, req.request.Method, req.request.URL.String()),
				}
			},
		},
	))

	return nil
}

// remoteAddrMW configures request tracing to capture the remote server address.
// Sets up client trace callbacks to extract and store the remote address
// of the server connection for later access. This information can be useful
// for logging, debugging, or connection analysis purposes.
func remoteAddrMW(req *Request) error {
	req.WithContext(httptrace.WithClientTrace(req.GetRequest().Context(),
		&httptrace.ClientTrace{
			GotConn: func(info httptrace.GotConnInfo) { req.remoteAddr = info.Conn.RemoteAddr() },
		},
	))

	return nil
}

// bearerAuthMW configures Bearer token authentication for HTTP requests.
// Adds an Authorization header with the Bearer token format if a token is provided.
// Only sets the header if the token is not empty, allowing conditional authentication.
func bearerAuthMW(req *Request, token g.String) error {
	if token.NotEmpty() {
		req.AddHeaders(g.Map[g.String, g.String]{header.AUTHORIZATION: "Bearer " + token})
	}

	return nil
}

// basicAuthMW configures HTTP Basic Authentication for requests.
// Expects authentication string in "username:password" format.
// Skips setting auth if Authorization header already exists.
// Returns an error if username or password fields are empty.
func basicAuthMW(req *Request, authentication g.String) error {
	if req.GetRequest().Header.Get(header.AUTHORIZATION) != "" {
		return nil
	}

	var username, password g.String

	authentication.Split(":").Collect().Unpack(&username, &password)

	if username == "" || password == "" {
		return errors.New("basic authorization fields cannot be empty")
	}

	req.GetRequest().SetBasicAuth(username.Std(), password.Std())

	return nil
}

// contentTypeMW configures the Content-Type header for HTTP requests.
// Sets the MIME type of the request body content to inform the server
// how to interpret the request data. Returns an error if contentType is empty.
func contentTypeMW(req *Request, contentType g.String) error {
	if contentType.Empty() {
		return fmt.Errorf("Content-Type is empty")
	}

	req.SetHeaders(g.Map[g.String, g.String]{header.CONTENT_TYPE: contentType})

	return nil
}
