package surf

import (
	"context"
	"fmt"
	"math"
	"math/rand"
	"net"

	"github.com/enetx/g"
	"github.com/enetx/http"
	"github.com/enetx/surf/internal/specclone"
	"github.com/enetx/surf/pkg/connectproxy"
	"github.com/enetx/surf/profiles/chrome"
	"github.com/enetx/surf/profiles/firefox"

	utls "github.com/enetx/utls"
)

// JA provides JA3/4 TLS fingerprinting capabilities for HTTP clients.
// JA is a method for creating SSL/TLS client fingerprints to identify and classify malware
// or other applications. This struct allows configuring various TLS ClientHello specifications
// to mimic different browsers and applications for advanced HTTP client behavior.
//
// Reference: https://lwthiker.com/networks/2022/06/17/tls-fingerprinting.html
type JA struct {
	spec    utls.ClientHelloSpec // Custom TLS ClientHello specification
	id      utls.ClientHelloID   // Predefined TLS ClientHello identifier
	builder *Builder             // Reference to the parent builder for method chaining
}

// SetHelloID sets a ClientHelloID for the TLS connection.
//
// The provided ClientHelloID is used to customize the TLS handshake. This
// should be a valid identifier that can be mapped to a specific ClientHelloSpec.
//
// It returns a pointer to the Options struct for method chaining. This allows
// additional configuration methods to be called on the result.
//
// Example usage:
//
//	JA().SetHelloID(utls.HelloChrome_Auto)
func (j *JA) SetHelloID(id utls.ClientHelloID) *Builder {
	j.id = id
	return j.build()
}

// SetHelloSpec sets a custom ClientHelloSpec for the TLS connection.
//
// This method allows you to set a custom ClientHelloSpec to be used during the TLS handshake.
// The provided spec should be a valid ClientHelloSpec.
//
// It returns a pointer to the Options struct for method chaining. This allows
// additional configuration methods to be called on the result.
//
// Example usage:
//
//	JA().SetHelloSpec(spec)
func (j *JA) SetHelloSpec(spec utls.ClientHelloSpec) *Builder {
	j.spec = spec
	return j.build()
}

// build applies JA3/4 TLS fingerprinting configuration to the HTTP client.
// This method configures the client with custom TLS settings and proxy support for JA3/4 fingerprinting.
//
// The method performs several key operations:
// 1. Skips configuration if HTTP/3 is being used (JA3/4 only works with HTTP/1.1 and HTTP/2)
// 2. Adds connection cleanup middleware if not using singleton pattern
// 3. Configures proxy settings for both static and dynamic proxy configurations
// 4. Wraps the transport with a custom round tripper that implements JA3/4 fingerprinting
//
// Returns the builder instance for method chaining.
func (j *JA) build() *Builder {
	return j.builder.addCliMW(func(c *Client) error {
		// JA3 fingerprinting is not compatible with HTTP/3 - skip if HTTP/3 is used
		if _, ok := c.GetTransport().(*uquicTransport); ok {
			return nil
		}

		if !j.builder.singleton {
			j.builder.addRespMW(closeIdleConnectionsMW, 0)
		}

		if j.builder.proxy != nil {
			var proxy string

			// Handle static proxy configurations
			switch v := j.builder.proxy.(type) {
			case string:
				proxy = v
			case g.String:
				proxy = v.Std()
			}

			if proxy != "" {
				// Static proxy configuration
				dialer, err := connectproxy.NewDialer(proxy)
				if err != nil {
					c.GetTransport().(*http.Transport).DialContext = func(context.Context, string, string) (net.Conn, error) {
						return nil, fmt.Errorf("proxy dialer init failed: %w", err)
					}
				} else {
					c.GetTransport().(*http.Transport).DialContext = dialer.DialContext
				}
			} else {
				// Dynamic proxy configuration - evaluate proxy per connection
				c.GetTransport().(*http.Transport).DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
					var proxy string

					switch v := j.builder.proxy.(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()
					}

					if proxy == "" {
						return c.GetDialer().DialContext(ctx, network, addr)
					}

					dialer, err := connectproxy.NewDialer(proxy)
					if err != nil {
						return nil, fmt.Errorf("create proxy dialer for %s: %w", proxy, err)
					}

					return dialer.DialContext(ctx, network, addr)
				}
			}
		}

		// Wrap the transport with JA3/4 fingerprinting round tripper
		c.GetClient().Transport = newRoundTripper(j, c.GetTransport())

		return nil
	}, math.MaxInt)
}

// getSpec determines the ClientHelloSpec to be used for the TLS connection.
//
// The ClientHelloSpec is selected based on the following order of precedence:
// 1. If a custom ClientHelloID is set (via SetHelloID), it attempts to convert this ID to a ClientHelloSpec.
// 2. If none of the above conditions are met, it returns the currently set ClientHelloSpec.
//
// This method returns the selected ClientHelloSpec along with an error value. If an error occurs
// during conversion, it returns the error.
func (j *JA) getSpec() g.Result[utls.ClientHelloSpec] {
	if !j.id.IsSet() {
		return g.ResultOf(utls.UTLSIdToSpec(j.id))
	}

	spec := specclone.Clone(&j.spec)
	return g.Ok(*spec)
}

// Browser and application fingerprinting methods.
// These methods provide convenient shortcuts to mimic various popular browsers and applications
// by setting predefined ClientHelloID values that match their TLS fingerprints.

// Android sets the JA3/4 fingerprint to mimic Android 11 OkHttp client.
func (j *JA) Android() *Builder { return j.SetHelloID(utls.HelloAndroid_11_OkHttp) }

// Chrome sets the JA3/4 fingerprint to mimic the latest Chrome browser (auto-detection).
func (j *JA) Chrome() *Builder { return j.SetHelloID(utls.HelloChrome_Auto) }

// Chrome58 sets the JA3/4 fingerprint to mimic Chrome version 58.
func (j *JA) Chrome58() *Builder { return j.SetHelloID(utls.HelloChrome_58) }

// Chrome62 sets the JA3/4 fingerprint to mimic Chrome version 62.
func (j *JA) Chrome62() *Builder { return j.SetHelloID(utls.HelloChrome_62) }

// Chrome70 sets the JA3/4 fingerprint to mimic Chrome version 70.
func (j *JA) Chrome70() *Builder { return j.SetHelloID(utls.HelloChrome_70) }

// Chrome72 sets the JA3/4 fingerprint to mimic Chrome version 72.
func (j *JA) Chrome72() *Builder { return j.SetHelloID(utls.HelloChrome_72) }

// Chrome83 sets the JA3/4 fingerprint to mimic Chrome version 83.
func (j *JA) Chrome83() *Builder { return j.SetHelloID(utls.HelloChrome_83) }

// Chrome87 sets the JA3/4 fingerprint to mimic Chrome version 87.
func (j *JA) Chrome87() *Builder { return j.SetHelloID(utls.HelloChrome_87) }

// Chrome96 sets the JA3/4 fingerprint to mimic Chrome version 96.
func (j *JA) Chrome96() *Builder { return j.SetHelloID(utls.HelloChrome_96) }

// Chrome100 sets the JA3/4 fingerprint to mimic Chrome version 100.
func (j *JA) Chrome100() *Builder { return j.SetHelloID(utls.HelloChrome_100) }

// Chrome102 sets the JA3/4 fingerprint to mimic Chrome version 102.
func (j *JA) Chrome102() *Builder { return j.SetHelloID(utls.HelloChrome_102) }

// Chrome106 sets the JA3/4 fingerprint to mimic Chrome version 106 with shuffled extensions.
func (j *JA) Chrome106() *Builder { return j.SetHelloID(utls.HelloChrome_106_Shuffle) }

// Chrome120 sets the JA3/4 fingerprint to mimic Chrome version 120.
func (j *JA) Chrome120() *Builder { return j.SetHelloID(utls.HelloChrome_120) }

// Chrome120PQ sets the JA3/4 fingerprint to mimic Chrome version 120 with post-quantum cryptography support.
func (j *JA) Chrome120PQ() *Builder { return j.SetHelloID(utls.HelloChrome_120_PQ) }

// Chrome142 sets the JA3/4 fingerprint to mimic Chrome version 142.
func (j *JA) Chrome142() *Builder { return j.SetHelloSpec(chrome.HelloChrome_142) }

// Edge sets the JA3/4 fingerprint to mimic Microsoft Edge version 85.
func (j *JA) Edge() *Builder { return j.SetHelloID(utls.HelloEdge_85) }

// Edge85 sets the JA3/4 fingerprint to mimic Microsoft Edge version 85.
func (j *JA) Edge85() *Builder { return j.SetHelloID(utls.HelloEdge_85) }

// Edge106 sets the JA3/4 fingerprint to mimic Microsoft Edge version 106.
func (j *JA) Edge106() *Builder { return j.SetHelloID(utls.HelloEdge_106) }

// Firefox sets the JA3/4 fingerprint to mimic the latest Firefox browser (auto-detection).
func (j *JA) Firefox() *Builder { return j.SetHelloID(utls.HelloFirefox_Auto) }

// Firefox55 sets the JA3/4 fingerprint to mimic Firefox version 55.
func (j *JA) Firefox55() *Builder { return j.SetHelloID(utls.HelloFirefox_55) }

// Firefox56 sets the JA3/4 fingerprint to mimic Firefox version 56.
func (j *JA) Firefox56() *Builder { return j.SetHelloID(utls.HelloFirefox_56) }

// Firefox63 sets the JA3/4 fingerprint to mimic Firefox version 63.
func (j *JA) Firefox63() *Builder { return j.SetHelloID(utls.HelloFirefox_63) }

// Firefox65 sets the JA3/4 fingerprint to mimic Firefox version 65.
func (j *JA) Firefox65() *Builder { return j.SetHelloID(utls.HelloFirefox_65) }

// Firefox99 sets the JA3/4 fingerprint to mimic Firefox version 99.
func (j *JA) Firefox99() *Builder { return j.SetHelloID(utls.HelloFirefox_99) }

// Firefox102 sets the JA3/4 fingerprint to mimic Firefox version 102.
func (j *JA) Firefox102() *Builder { return j.SetHelloID(utls.HelloFirefox_102) }

// Firefox105 sets the JA3/4 fingerprint to mimic Firefox version 105.
func (j *JA) Firefox105() *Builder { return j.SetHelloID(utls.HelloFirefox_105) }

// Firefox120 sets the JA3/4 fingerprint to mimic Firefox version 120.
func (j *JA) Firefox120() *Builder { return j.SetHelloID(utls.HelloFirefox_120) }

// Firefox141 sets the JA3/4 fingerprint to mimic Firefox version 141.
func (j *JA) Firefox141() *Builder { return j.SetHelloID(utls.HelloFirefox_141) }

// Firefox144 sets the JA3/4 fingerprint to mimic Firefox version 144.
func (j *JA) Firefox144() *Builder { return j.SetHelloSpec(firefox.HelloFirefox_144) }

// FirefoxPrivate144 sets the JA3/4 fingerprint to mimic Firefox private version 144.
func (j *JA) FirefoxPrivate144() *Builder { return j.SetHelloSpec(firefox.HelloFirefoxPrivate_144) }

// Tor sets the JA3/4 fingerprint to mimic Tor Browser version 14.5.6.
func (j *JA) Tor() *Builder { return j.SetHelloSpec(firefox.Tor) }

// TorPrivate sets the JA3/4 fingerprint to mimic Tor Browser private version 14.5.6.
func (j *JA) TorPrivate() *Builder { return j.SetHelloSpec(firefox.TorPrivate) }

// IOS sets the JA3/4 fingerprint to mimic the latest iOS Safari browser (auto-detection).
func (j *JA) IOS() *Builder { return j.SetHelloID(utls.HelloIOS_Auto) }

// IOS11 sets the JA3/4 fingerprint to mimic iOS 11.1 Safari.
func (j *JA) IOS11() *Builder { return j.SetHelloID(utls.HelloIOS_11_1) }

// IOS12 sets the JA3/4 fingerprint to mimic iOS 12.1 Safari.
func (j *JA) IOS12() *Builder { return j.SetHelloID(utls.HelloIOS_12_1) }

// IOS13 sets the JA3/4 fingerprint to mimic iOS 13 Safari.
func (j *JA) IOS13() *Builder { return j.SetHelloID(utls.HelloIOS_13) }

// IOS14 sets the JA3/4 fingerprint to mimic iOS 14 Safari.
func (j *JA) IOS14() *Builder { return j.SetHelloID(utls.HelloIOS_14) }

// Randomized sets a completely randomized JA3/4 fingerprint.
func (j *JA) Randomized() *Builder { return j.SetHelloID(utls.HelloRandomized) }

// RandomizedALPN sets a randomized JA3/4 fingerprint with ALPN (Application-Layer Protocol Negotiation).
func (j *JA) RandomizedALPN() *Builder { return j.SetHelloID(utls.HelloRandomizedALPN) }

// RandomizedNoALPN sets a randomized JA3/4 fingerprint without ALPN.
func (j *JA) RandomizedNoALPN() *Builder { return j.SetHelloID(utls.HelloRandomizedNoALPN) }

// Safari sets the JA3/4 fingerprint to mimic the latest Safari browser (auto-detection).
func (j *JA) Safari() *Builder { return j.SetHelloID(utls.HelloSafari_Auto) }
