package surf

import (
	"context"
	"crypto/tls"
	"fmt"
	"maps"
	"math/rand"
	"net"
	"net/url"
	"time"

	"github.com/enetx/g"
	"github.com/enetx/http"
	"github.com/enetx/http/cookiejar"
	"github.com/enetx/http2"
	"golang.org/x/net/publicsuffix"
)

// defaultDialerMW initializes the default network dialer for the surf client.
// Sets up timeout and keep-alive configuration for TCP connections.
func defaultDialerMW(client *Client) error {
	client.dialer = &net.Dialer{Timeout: _dialerTimeout, KeepAlive: _TCPKeepAlive}
	return nil
}

// defaultTLSConfigMW initializes the default TLS configuration for the surf client.
// Configures TLS settings with insecure skip verify enabled by default for flexibility.
func defaultTLSConfigMW(client *Client) error {
	client.tlsConfig = &tls.Config{InsecureSkipVerify: true}
	return nil
}

// defaultTransportMW initializes the default HTTP transport for the surf client.
// Configures connection pooling, timeouts, and enables HTTP/2 support by default.
func defaultTransportMW(client *Client) error {
	transport := http.DefaultTransport.(*http.Transport).Clone()
	transport.DialContext = client.dialer.DialContext
	transport.TLSClientConfig = client.tlsConfig
	transport.MaxIdleConns = _maxIdleConns
	transport.MaxConnsPerHost = _maxConnsPerHost
	transport.MaxIdleConnsPerHost = _maxIdleConnsPerHost
	transport.IdleConnTimeout = _idleConnTimeout
	transport.ForceAttemptHTTP2 = true

	client.transport = transport

	return nil
}

// defaultClientMW initializes the default HTTP client for the surf client.
// Sets up the HTTP client with the configured transport and timeout settings.
func defaultClientMW(client *Client) error {
	client.cli = &http.Client{Transport: client.transport, Timeout: _clientTimeout}
	return nil
}

// boundaryMW sets a custom boundary function for multipart form data.
// The boundary function is called to generate unique boundaries for multipart requests.
func boundaryMW(client *Client, boundary func() g.String) error {
	client.boundary = boundary
	return nil
}

// forseHTTP1MW configures the client to use HTTP/1.1 forcefully.
// Disables HTTP/2 and forces the client to use only HTTP/1.1 protocol.
func forseHTTP1MW(client *Client) error {
	transport := client.GetTransport().(*http.Transport)
	transport.Protocols = new(http.Protocols)
	transport.Protocols.SetHTTP1(true)
	return nil
}

// sessionMW configures the client's cookie jar for session management.
// It initializes a new cookie jar and sets up the TLS configuration
// to manage client sessions efficiently.
func sessionMW(client *Client) error {
	client.GetClient().Jar, _ = cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
	client.GetTLSConfig().ClientSessionCache = tls.NewLRUClientSessionCache(0)
	return nil
}

// disableKeepAliveMW disables the keep-alive setting for the client's transport.
func disableKeepAliveMW(client *Client) error {
	client.GetTransport().(*http.Transport).DisableKeepAlives = true
	return nil
}

// disableCompressionMW disables compression for the client's transport.
func disableCompressionMW(client *Client) error {
	client.GetTransport().(*http.Transport).DisableCompression = true
	return nil
}

// interfaceAddrMW configures the client's local network interface address for outbound connections.
// This allows binding the client to a specific network interface or IP address for dialing.
// Useful for systems with multiple network interfaces or for controlling which IP address to use.
func interfaceAddrMW(client *Client, address g.String) error {
	if address != "" {
		ip, err := net.ResolveTCPAddr("tcp", address.Std()+":0")
		if err != nil {
			return err
		}

		client.GetDialer().LocalAddr = ip
	}

	return nil
}

// timeoutMW configures the client's overall request timeout.
// This sets the maximum duration for entire HTTP requests including connection,
// request transmission, and response reading.
func timeoutMW(client *Client, timeout time.Duration) error {
	client.GetClient().Timeout = timeout
	return nil
}

// redirectPolicyMW configures the client's HTTP redirect handling behavior.
// Sets up redirect policies including maximum redirect count, host-only redirects,
// header forwarding on redirects, and custom redirect functions.
func redirectPolicyMW(client *Client) error {
	builder := client.builder
	maxRedirects := _maxRedirects

	if builder != nil {
		// Use custom redirect function if provided
		if builder.checkRedirect != nil {
			client.GetClient().CheckRedirect = builder.checkRedirect
			return nil
		}

		// Override default max redirects if specified
		if builder.maxRedirects != 0 {
			maxRedirects = builder.maxRedirects
		}
	}

	// Set up default redirect policy with configured behavior
	client.GetClient().CheckRedirect = func(req *http.Request, via []*http.Request) error {
		// Stop redirecting if maximum redirect count is exceeded
		if len(via) >= maxRedirects {
			return http.ErrUseLastResponse
		}

		if builder != nil {
			// Only follow redirects within the same host if configured
			if builder.followOnlyHostRedirects {
				newHost := req.URL.Host
				oldHost := via[0].Host

				if oldHost == "" {
					oldHost = via[0].URL.Host
				}

				if newHost != oldHost {
					return http.ErrUseLastResponse
				}
			}

			// Forward headers from original request to redirect if configured
			if builder.forwardHeadersOnRedirect {
				maps.Copy(req.Header, via[0].Header)
			}
		}

		return nil
	}

	return nil
}

// dnsMW configures a custom DNS server for the client.
// Sets up the client to use the specified DNS server address for hostname resolution
// instead of the system's default DNS configuration.
func dnsMW(client *Client, dns g.String) error {
	if dns.Empty() {
		return nil
	}

	client.GetDialer().Resolver = &net.Resolver{
		PreferGo: true,
		Dial: func(ctx context.Context, _, _ string) (net.Conn, error) {
			var dialer net.Dialer
			return dialer.DialContext(ctx, "udp", dns.Std())
		},
	}

	return nil
}

// dnsTLSMW configures DNS over TLS (DoT) for the client.
// Replaces the default DNS resolver with a secure DNS-over-TLS resolver
// to encrypt DNS queries and protect against DNS manipulation.
func dnsTLSMW(client *Client, resolver *net.Resolver) error {
	client.GetDialer().Resolver = resolver
	return nil
}

// unixSocketMW configures the client to connect via Unix domain sockets.
// Replaces the standard TCP connection with Unix socket communication,
// useful for connecting to local services that expose Unix socket interfaces.
func unixSocketMW(client *Client, address g.String) error {
	if address.Empty() {
		return nil
	}

	client.GetTransport().(*http.Transport).DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
		return (&net.Dialer{}).DialContext(ctx, "unix", address.Std())
	}

	return nil
}

// proxyMW configures HTTP proxy settings for the client transport.
// Supports both static proxy configurations (string URLs) and dynamic proxy providers
// (functions that return proxy URLs for rotation). Handles various proxy types including
// HTTP, HTTPS, and SOCKS proxies. Skips configuration for JA3 and HTTP/3 transports
// which handle proxies differently.
func proxyMW(client *Client, proxys any) error {
	// Skip proxy configuration for JA3 transport (handled separately)
	if client.builder.ja {
		return nil
	}

	// Skip if HTTP/3 transport is being used (handled separately)
	if _, ok := client.GetTransport().(*uquicTransport); ok {
		return nil
	}

	transport, ok := client.GetTransport().(*http.Transport)
	if !ok {
		return fmt.Errorf("transport is not *http.Transport")
	}

	// Clear proxy if nil provided
	if proxys == nil {
		transport.Proxy = nil
		return nil
	}

	// Helper function to set static proxy
	setProxy := func(proxy string) {
		if proxy == "" {
			return
		}
		if proxyURL, err := url.Parse(proxy); err == nil {
			transport.Proxy = http.ProxyURL(proxyURL)
		} else {
			transport.Proxy = func(*http.Request) (*url.URL, error) {
				return nil, fmt.Errorf("invalid proxy URL %q: %w", proxy, err)
			}
		}
	}

	// Handle static proxy configurations
	switch v := proxys.(type) {
	case string:
		setProxy(v)
		return nil
	case g.String:
		setProxy(v.Std())
		return nil
	}

	// Handle dynamic proxy configurations - evaluate proxy per request
	transport.Proxy = func(*http.Request) (*url.URL, error) {
		var proxy string

		switch v := proxys.(type) {
		case func() g.String:
			proxy = v().Std()
		case []string:
			if len(v) > 0 {
				proxy = v[rand.Intn(len(v))]
			}
		case g.Slice[string]:
			proxy = v.Random()
		case g.Slice[g.String]:
			proxy = v.Random().Std()
		default:
			return nil, fmt.Errorf("unsupported proxy type: %T", proxys)
		}

		if proxy == "" {
			return nil, nil
		}

		return url.Parse(proxy)
	}

	return nil
}

// h2cMW configures HTTP/2 Cleartext (H2C) support for the client.
// H2C allows HTTP/2 communication over plain text connections without TLS.
// This is useful for internal communication or development scenarios where TLS is not required.
// Skips configuration if HTTP/3 transport is being used as they are incompatible.
func h2cMW(client *Client) error {
	// H2C is incompatible with HTTP/3 transport - skip if HTTP/3 is being used
	if _, ok := client.transport.(*uquicTransport); ok {
		return nil
	}

	t2 := new(http2.Transport)

	// Configure H2C specific settings
	t2.AllowHTTP = true
	t2.DisableCompression = client.GetTransport().(*http.Transport).DisableCompression
	t2.IdleConnTimeout = client.transport.(*http.Transport).IdleConnTimeout

	// Override TLS dial to use plain text connections
	t2.DialTLSContext = func(_ context.Context, network, addr string, _ *tls.Config) (net.Conn, error) {
		return net.Dial(network, addr)
	}

	// Apply HTTP/2 settings if configured
	if client.builder.http2settings != nil {
		h := client.builder.http2settings

		// Helper function to append non-zero settings
		appendSetting := func(id http2.SettingID, val uint32) {
			if val != 0 || (id == http2.SettingEnablePush && h.usePush) {
				t2.Settings = append(t2.Settings, http2.Setting{ID: id, Val: val})
			}
		}

		// Apply all configured HTTP/2 settings
		settings := [...]struct {
			id  http2.SettingID
			val uint32
		}{
			{http2.SettingHeaderTableSize, h.headerTableSize},
			{http2.SettingEnablePush, h.enablePush},
			{http2.SettingMaxConcurrentStreams, h.maxConcurrentStreams},
			{http2.SettingInitialWindowSize, h.initialWindowSize},
			{http2.SettingMaxFrameSize, h.maxFrameSize},
			{http2.SettingMaxHeaderListSize, h.maxHeaderListSize},
		}

		for _, s := range settings {
			appendSetting(s.id, s.val)
		}

		// Apply flow control settings if configured
		if h.connectionFlow != 0 {
			t2.ConnectionFlow = h.connectionFlow
		}

		// Apply priority settings if configured
		if !h.priorityParam.IsZero() {
			t2.PriorityParam = h.priorityParam
		}

		if h.priorityFrames != nil {
			t2.PriorityFrames = h.priorityFrames
		}
	}

	client.cli.Transport = t2

	return nil
}
