package traefikoidc

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"net"
	"net/http"
	"net/http/cookiejar"
	"time"
)

// HTTPClientConfig provides configuration for creating HTTP clients
type HTTPClientConfig struct {
	IdleConnTimeout       time.Duration
	MaxIdleConns          int
	ReadBufferSize        int
	DialTimeout           time.Duration
	KeepAlive             time.Duration
	TLSHandshakeTimeout   time.Duration
	ResponseHeaderTimeout time.Duration
	ExpectContinueTimeout time.Duration
	MaxRedirects          int
	MaxIdleConnsPerHost   int
	Timeout               time.Duration
	MaxConnsPerHost       int
	WriteBufferSize       int
	// RootCAs is an optional certificate pool used for TLS verification.
	// A nil pool means "use the system trust store" (default behavior).
	RootCAs *x509.CertPool
	// InsecureSkipVerify disables TLS certificate verification.
	// ONLY set this for local development against self-signed certificates.
	InsecureSkipVerify bool
	UseCookieJar       bool
	ForceHTTP2         bool
	DisableKeepAlives  bool
	DisableCompression bool
}

// DefaultHTTPClientConfig returns the default configuration for general use
func DefaultHTTPClientConfig() HTTPClientConfig {
	return HTTPClientConfig{
		Timeout:               10 * time.Second, // SECURITY FIX: Reduced from 30s to prevent slowloris attacks
		MaxRedirects:          5,                // SECURITY FIX: Reduced from 10 to prevent redirect loops
		UseCookieJar:          false,
		DialTimeout:           3 * time.Second, // SECURITY FIX: Reduced from 5s
		KeepAlive:             15 * time.Second,
		TLSHandshakeTimeout:   2 * time.Second,
		ResponseHeaderTimeout: 3 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
		IdleConnTimeout:       30 * time.Second, // OPTIMIZATION: Increased for better connection reuse
		MaxIdleConns:          50,               // OPTIMIZATION: Increased from 20 for better connection pooling
		MaxIdleConnsPerHost:   10,               // OPTIMIZATION: Increased from 2 for better connection reuse
		MaxConnsPerHost:       20,               // OPTIMIZATION: Increased from 5 while maintaining security
		WriteBufferSize:       4096,
		ReadBufferSize:        4096,
		ForceHTTP2:            true,
		DisableKeepAlives:     false,
		DisableCompression:    false,
	}
}

// TokenHTTPClientConfig returns configuration optimized for token operations
func TokenHTTPClientConfig() HTTPClientConfig {
	config := DefaultHTTPClientConfig()
	config.Timeout = 10 * time.Second // Shorter timeout for token operations
	config.MaxRedirects = 50          // Token endpoints may redirect more
	config.UseCookieJar = true        // Enable cookie jar for token operations
	return config
}

// OIDCProviderHTTPClientConfig returns configuration optimized for OIDC provider calls
func OIDCProviderHTTPClientConfig() HTTPClientConfig {
	config := DefaultHTTPClientConfig()
	config.Timeout = 15 * time.Second         // Slightly longer for OIDC operations
	config.MaxIdleConns = 100                 // Higher pool for frequent OIDC calls
	config.MaxIdleConnsPerHost = 25           // More connections per OIDC provider
	config.MaxConnsPerHost = 50               // Allow more concurrent requests to OIDC provider
	config.IdleConnTimeout = 90 * time.Second // Keep connections alive longer for reuse
	config.UseCookieJar = true                // Enable cookie jar for session management
	return config
}

// HTTPClientFactory provides methods for creating configured HTTP clients
type HTTPClientFactory struct{}

// NewHTTPClientFactory creates a new HTTP client factory
func NewHTTPClientFactory() *HTTPClientFactory {
	return &HTTPClientFactory{}
}

// ValidateHTTPClientConfig validates HTTP client configuration parameters
func (f *HTTPClientFactory) ValidateHTTPClientConfig(config *HTTPClientConfig) error {
	// Validate connection pool limits
	if config.MaxIdleConns < 0 {
		return fmt.Errorf("MaxIdleConns cannot be negative: %d", config.MaxIdleConns)
	}
	if config.MaxIdleConns > 1000 {
		return fmt.Errorf("MaxIdleConns too high (max 1000): %d", config.MaxIdleConns)
	}

	if config.MaxIdleConnsPerHost < 0 {
		return fmt.Errorf("MaxIdleConnsPerHost cannot be negative: %d", config.MaxIdleConnsPerHost)
	}
	if config.MaxIdleConnsPerHost > 100 {
		return fmt.Errorf("MaxIdleConnsPerHost too high (max 100): %d", config.MaxIdleConnsPerHost)
	}

	if config.MaxConnsPerHost < 0 {
		return fmt.Errorf("MaxConnsPerHost cannot be negative: %d", config.MaxConnsPerHost)
	}
	if config.MaxConnsPerHost > 100 {
		return fmt.Errorf("MaxConnsPerHost too high (max 100): %d", config.MaxConnsPerHost)
	}

	// Validate that MaxIdleConnsPerHost is not greater than MaxConnsPerHost
	if config.MaxIdleConnsPerHost > config.MaxConnsPerHost && config.MaxConnsPerHost > 0 {
		return fmt.Errorf("MaxIdleConnsPerHost (%d) cannot exceed MaxConnsPerHost (%d)",
			config.MaxIdleConnsPerHost, config.MaxConnsPerHost)
	}

	// Validate timeout values
	if config.Timeout <= 0 {
		return fmt.Errorf("timeout must be positive: %v", config.Timeout)
	}
	if config.Timeout > 5*time.Minute {
		return fmt.Errorf("timeout too high (max 5m): %v", config.Timeout)
	}

	if config.DialTimeout <= 0 {
		return fmt.Errorf("DialTimeout must be positive: %v", config.DialTimeout)
	}
	if config.TLSHandshakeTimeout <= 0 {
		return fmt.Errorf("TLSHandshakeTimeout must be positive: %v", config.TLSHandshakeTimeout)
	}

	return nil
}

// CreateHTTPClient creates an HTTP client with the given configuration
// Validates configuration parameters before creating the client
func (f *HTTPClientFactory) CreateHTTPClient(config HTTPClientConfig) *http.Client {
	// Set defaults for zero values before validation
	if config.Timeout == 0 {
		config.Timeout = 30 * time.Second
	}
	if config.DialTimeout == 0 {
		config.DialTimeout = 5 * time.Second
	}
	if config.TLSHandshakeTimeout == 0 {
		config.TLSHandshakeTimeout = 2 * time.Second
	}
	if config.KeepAlive == 0 {
		config.KeepAlive = 15 * time.Second
	}
	if config.ResponseHeaderTimeout == 0 {
		config.ResponseHeaderTimeout = 3 * time.Second
	}
	if config.ExpectContinueTimeout == 0 {
		config.ExpectContinueTimeout = 1 * time.Second
	}
	if config.IdleConnTimeout == 0 {
		config.IdleConnTimeout = 5 * time.Second
	}
	if config.MaxIdleConns == 0 {
		config.MaxIdleConns = 100
	}
	if config.MaxIdleConnsPerHost == 0 {
		config.MaxIdleConnsPerHost = 10
	}
	if config.MaxConnsPerHost == 0 {
		config.MaxConnsPerHost = 10
	}
	if config.WriteBufferSize == 0 {
		config.WriteBufferSize = 4096
	}
	if config.ReadBufferSize == 0 {
		config.ReadBufferSize = 4096
	}

	// Validate configuration - only fail on critical errors
	if err := f.ValidateHTTPClientConfig(&config); err != nil {
		// Only use default config for critical validation failures
		// For example, if timeout is negative or extremely high
		if config.Timeout <= 0 || config.Timeout > 5*time.Minute {
			config.Timeout = 30 * time.Second
		}
	}
	// Create transport with configured settings
	transport := &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
			dialer := &net.Dialer{
				Timeout:   config.DialTimeout,
				KeepAlive: config.KeepAlive,
			}
			return dialer.DialContext(ctx, network, addr)
		},
		// SECURITY FIX: Enforce TLS 1.2+ and secure cipher suites
		TLSClientConfig: &tls.Config{
			MinVersion: tls.VersionTLS12, // Enforce TLS 1.2 minimum
			MaxVersion: tls.VersionTLS13, // Support up to TLS 1.3
			CipherSuites: []uint16{
				// TLS 1.3 cipher suites (automatically selected when TLS 1.3 is negotiated)
				// TLS 1.2 secure cipher suites
				tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
				tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
			},
			PreferServerCipherSuites: true,
			RootCAs:                  config.RootCAs,
			InsecureSkipVerify:       config.InsecureSkipVerify, //nolint:gosec // opt-in, loud warning emitted at plugin startup
		},
		ForceAttemptHTTP2:     config.ForceHTTP2,
		TLSHandshakeTimeout:   config.TLSHandshakeTimeout,
		ExpectContinueTimeout: config.ExpectContinueTimeout,
		MaxIdleConns:          config.MaxIdleConns,
		MaxIdleConnsPerHost:   config.MaxIdleConnsPerHost,
		IdleConnTimeout:       config.IdleConnTimeout,
		DisableKeepAlives:     config.DisableKeepAlives,
		MaxConnsPerHost:       config.MaxConnsPerHost,
		ResponseHeaderTimeout: config.ResponseHeaderTimeout,
		DisableCompression:    config.DisableCompression,
		WriteBufferSize:       config.WriteBufferSize,
		ReadBufferSize:        config.ReadBufferSize,
	}

	client := &http.Client{
		Timeout:   config.Timeout,
		Transport: transport,
	}

	// Configure redirect policy
	maxRedirects := config.MaxRedirects
	if maxRedirects == 0 {
		maxRedirects = 10 // Go's default
	}
	client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
		if len(via) >= maxRedirects {
			return fmt.Errorf("stopped after %d redirects", maxRedirects)
		}
		return nil
	}

	// Add cookie jar if requested
	if config.UseCookieJar {
		jar, _ := cookiejar.New(nil) // Safe to ignore: cookiejar creation with nil options rarely fails
		client.Jar = jar
	}

	return client
}

// CreateDefaultClient creates a client with default configuration
func (f *HTTPClientFactory) CreateDefaultClient() *http.Client {
	return f.CreateHTTPClient(DefaultHTTPClientConfig())
}

// CreateTokenClient creates a client optimized for token operations
func (f *HTTPClientFactory) CreateTokenClient() *http.Client {
	return f.CreateHTTPClient(TokenHTTPClientConfig())
}

// Global factory instance for convenience
var globalHTTPClientFactory = NewHTTPClientFactory()

// CreateHTTPClientWithConfig creates an HTTP client with the given configuration
// using the global factory instance
func CreateHTTPClientWithConfig(config HTTPClientConfig) *http.Client {
	return globalHTTPClientFactory.CreateHTTPClient(config)
}

// CreateDefaultHTTPClient creates a default HTTP client using the global factory
func CreateDefaultHTTPClient() *http.Client {
	// Use pooled client to prevent connection exhaustion
	return CreatePooledHTTPClient(DefaultHTTPClientConfig())
}

// CreateTokenHTTPClient creates a token HTTP client using the global factory
func CreateTokenHTTPClient() *http.Client {
	// Use pooled client to prevent connection exhaustion
	return CreatePooledHTTPClient(TokenHTTPClientConfig())
}
