package audit

import (
	"net"
	"net/http"
	"strings"
	"time"
)

// Entry represents a single audit log entry.
type Entry struct {
	Timestamp      string `json:"timestamp"`
	ClientIP       string `json:"client_ip"`
	RequestMethod  string `json:"request_method"`
	RequestURI     string `json:"request_uri"`
	RequestParams  string `json:"request_params,omitempty"`
	RequestBody    string `json:"request_body,omitempty"`
	ResponseStatus int    `json:"response_status"`
	LatencyMs      int64  `json:"latency_ms"`
	Error          string `json:"error,omitempty"`
}

// baseSensitiveFields contains credential-related fields common to both query params and body.
var baseSensitiveFields = map[string]struct{}{
	"password":      {},
	"passwd":        {},
	"pwd":           {},
	"secret":        {},
	"token":         {},
	"access_token":  {},
	"refresh_token": {},
	"api_key":       {},
	"apikey":        {},
	"private_key":   {},
	"credential":    {},
	"credentials":   {},
	"secret_key":    {},
	"client_secret": {},
}

// additionalQueryParamFields lists fields specific to query parameters.
var additionalQueryParamFields = map[string]struct{}{
	"access_key":    {},
	"session_token": {},
	"auth":          {},
}

// defaultSensitiveQueryParams combines base fields with query-specific fields.
var defaultSensitiveQueryParams = func() map[string]struct{} {
	result := make(map[string]struct{}, len(baseSensitiveFields)+len(additionalQueryParamFields))
	for k := range baseSensitiveFields {
		result[k] = struct{}{}
	}
	for k := range additionalQueryParamFields {
		result[k] = struct{}{}
	}
	return result
}()

const (
	redactedValue = "[REDACTED]"

	// truncatedSuffix is appended when body is truncated.
	truncatedSuffix = "...[truncated]"

	// maxRecursionDepth limits the recursion depth when redacting sensitive fields.
	maxRecursionDepth = 20
)

// ExtractClientIP extracts client IP from request, considering X-Forwarded-For header.
// Note: This function unconditionally trusts X-Forwarded-For and X-Real-IP headers.
// This is intentional because service-proxy is designed to always run behind a trusted
// load balancer or ingress controller that sets these headers. Do not deploy this service
// directly exposed to the internet without a trusted proxy in front.
func ExtractClientIP(r *http.Request) string {
	// Check X-Forwarded-For header first
	if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
		// X-Forwarded-For can contain multiple IPs: client, proxy1, proxy2, ...
		// The first IP is the original client IP
		ips := strings.Split(xff, ",")
		if len(ips) > 0 {
			clientIP := strings.TrimSpace(ips[0])
			if clientIP != "" {
				return clientIP
			}
		}
	}

	// Check X-Real-IP header
	if xri := r.Header.Get("X-Real-IP"); xri != "" {
		return strings.TrimSpace(xri)
	}

	// Fall back to RemoteAddr
	ip, _, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		// RemoteAddr might not have port
		return r.RemoteAddr
	}
	return ip
}

// RequestRecordOptions defines the data needed to record an HTTP audit entry.
type RequestRecordOptions struct {
	Start         time.Time
	StatusCode    int
	Error         string
	OriginalPath  string
	RawQuery      string
	Body          []byte
	BodyTruncated bool // Set to true if body was truncated during capture
}

// RecordRequest logs an HTTP request/response pair to the audit logger.
func (l *Logger) RecordRequest(r *http.Request, opts RequestRecordOptions) {
	if l == nil || r == nil {
		return
	}

	// Use hot config for all runtime decisions
	if !l.hot.getIncludeGetRequests() && r.Method == http.MethodGet {
		return
	}

	path := opts.OriginalPath
	if path == "" {
		path = r.URL.Path
	}

	rawQuery := opts.RawQuery
	if rawQuery == "" {
		rawQuery = r.URL.RawQuery
	}

	maxBodySize := l.hot.getMaxBodySize()
	sanitizer := l.hot.getSanitizer()

	requestBody := ""
	// Policy:
	// 1) Only log request bodies when response is successful (< 400).
	// 2) MaxBodySize <= 0 disables request body logging.
	if opts.StatusCode < http.StatusBadRequest && maxBodySize > 0 {
		requestBody = sanitizer.SanitizeBody(opts.Body, maxBodySize, opts.BodyTruncated)
	}

	entry := &Entry{
		Timestamp:      opts.Start.UTC().Format(time.RFC3339Nano),
		ClientIP:       ExtractClientIP(r),
		RequestMethod:  r.Method,
		RequestURI:     sanitizer.SanitizePath(path),
		RequestParams:  sanitizer.SanitizeQueryParams(rawQuery),
		RequestBody:    requestBody,
		ResponseStatus: opts.StatusCode,
		LatencyMs:      time.Since(opts.Start).Milliseconds(),
		Error:          opts.Error,
	}

	l.Log(entry)
}
