package verbose

import (
	"bytes"
	"crypto/tls"
	"io"
	"net/http"
	"time"
)

// maxBodyLog is the maximum number of bytes to log from request/response bodies.
const maxBodyLog = 4096

// Transport wraps an http.RoundTripper and logs request/response details
// when verbose mode is enabled. Sensitive headers and credential fields
// are redacted. When verbose is off, it passes through with zero overhead.
type Transport struct {
	// Base is the underlying transport. If nil, http.DefaultTransport is used.
	Base http.RoundTripper
}

// MetadataTransport logs request method, URL, and response status without
// reading headers or bodies. It is safe for credential-bearing AWS endpoints.
type MetadataTransport struct {
	Base http.RoundTripper
}

// WrapTransport returns a Transport that logs when verbose is enabled.
func WrapTransport(base http.RoundTripper) http.RoundTripper {
	if base == nil {
		base = http.DefaultTransport
	}
	return &Transport{Base: base}
}

// NewHTTPClient returns an http.Client with verbose logging transport and the given timeout.
func NewHTTPClient(timeout time.Duration) *http.Client {
	return &http.Client{
		Timeout:   timeout,
		Transport: WrapTransport(DefaultTransport()),
	}
}

// NewMetadataHTTPClient retains verbose request diagnostics while ensuring
// credential-provider and service response bodies never reach logs.
func NewMetadataHTTPClient(timeout time.Duration) *http.Client {
	return &http.Client{
		Timeout:   timeout,
		Transport: &MetadataTransport{Base: DefaultTransport()},
	}
}

// DefaultTransport returns a clone of Go's default transport with HTTP/2
// disabled. The AWS EC2 Query API has shown cancellation stalls over HTTP/2
// during cleanup; HTTP/1.1 keeps per-request timeouts reliable.
func DefaultTransport() http.RoundTripper {
	base, ok := http.DefaultTransport.(*http.Transport)
	if !ok {
		return http.DefaultTransport
	}
	transport := base.Clone()
	transport.ForceAttemptHTTP2 = false
	transport.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{}
	if transport.TLSClientConfig == nil {
		transport.TLSClientConfig = &tls.Config{MinVersion: tls.VersionTLS12}
	} else {
		transport.TLSClientConfig = transport.TLSClientConfig.Clone()
	}
	transport.TLSClientConfig.NextProtos = []string{"http/1.1"}
	return transport
}

// sensitiveHeaders are HTTP headers whose values should be redacted in logs.
var sensitiveHeaders = map[string]bool{
	"Authorization":       true,
	"Proxy-Authorization": true,
	"Cookie":              true,
	"Set-Cookie":          true,
}

// redactHeaders returns a sanitized copy of headers for logging.
func redactHeaders(h http.Header) http.Header {
	out := h.Clone()
	for name := range out {
		if sensitiveHeaders[http.CanonicalHeaderKey(name)] {
			out.Set(name, "***REDACTED***")
		}
	}
	return out
}

// RoundTrip implements http.RoundTripper.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	if !Enabled() {
		return t.base().RoundTrip(req)
	}

	Logf("HTTP %s %s", req.Method, req.URL)
	Logf("HTTP >>> headers: %v", redactHeaders(req.Header))

	// Log request body if present.
	if req.Body != nil && req.Body != http.NoBody {
		bodyBytes, readErr := io.ReadAll(req.Body)
		_ = req.Body.Close()
		if readErr == nil {
			logBody("HTTP >>> body", bodyBytes)
			req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
		}
	}

	start := time.Now()
	resp, rtErr := t.base().RoundTrip(req)
	elapsed := time.Since(start)

	if rtErr != nil {
		Logf("HTTP <<< ERROR (%s): %v", elapsed, rtErr)
		return resp, rtErr
	}

	Logf("HTTP <<< status=%d (%s) headers: %v", resp.StatusCode, elapsed, redactHeaders(resp.Header))

	// Log response body, then re-wrap for callers.
	if resp.Body != nil {
		bodyBytes, readErr := io.ReadAll(resp.Body)
		_ = resp.Body.Close()
		if readErr == nil {
			logBody("HTTP <<< body", bodyBytes)
			resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
		}
	}

	return resp, rtErr
}

// RoundTrip implements http.RoundTripper without inspecting headers or bodies.
func (t *MetadataTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	base := t.Base
	if base == nil {
		base = http.DefaultTransport
	}
	if !Enabled() {
		return base.RoundTrip(req)
	}

	Logf("HTTP %s %s", req.Method, req.URL)
	start := time.Now()
	resp, err := base.RoundTrip(req)
	elapsed := time.Since(start)
	if err != nil {
		Logf("HTTP <<< ERROR (%s): %v", elapsed, err)
		return resp, err
	}
	Logf("HTTP <<< status=%d (%s)", resp.StatusCode, elapsed)
	return resp, nil
}

// logBody logs a body with a prefix, truncating if it exceeds maxBodyLog.
func logBody(prefix string, body []byte) {
	if len(body) == 0 {
		return
	}
	if len(body) > maxBodyLog {
		Logf("%s: (%d bytes, showing first %d) %s", prefix, len(body), maxBodyLog, string(body[:maxBodyLog]))
	} else {
		Logf("%s: %s", prefix, string(body))
	}
}

func (t *Transport) base() http.RoundTripper {
	if t.Base != nil {
		return t.Base
	}
	return http.DefaultTransport
}
