package middleware

import (
	"fmt"
	"log"
	"net/url"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
)

var sensitiveQueryKeys = map[string]struct{}{
	"token":         {},
	"access_token":  {},
	"refresh_token": {},
	"code":          {},
	"secret":        {},
}

// logEntry is a single access-log line sent to the async writer.
type logEntry struct {
	ts        time.Time
	method    string
	path      string
	rawQuery  string
	status    int
	latency   time.Duration
	ip        string
	userAgent string
	userID    interface{}
	traceID   string
	errors    []string
}

// asyncLogger holds a channel-based write queue so that access log I/O is
// decoupled from the HTTP request handling goroutine.
//
// Buffer depth of 4096 means we can absorb ~4 k outstanding log lines before
// back-pressure is applied.  Under normal load (< 500 req/s) this buffer never
// fills; under load-test peaks it prevents log writes from inflating p99.
var asyncLogCh = func() chan logEntry {
	ch := make(chan logEntry, 4096)
	go func() {
		for e := range ch {
			writeLogEntry(e)
		}
	}()
	return ch
}()

func writeLogEntry(e logEntry) {
	log.Printf("[API] %s %s %s | status: %d | latency: %v | ip: %s | user_id: %v | trace_id: %s | ua: %s",
		e.ts.Format("2006-01-02 15:04:05"),
		e.method,
		e.path,
		e.status,
		e.latency,
		e.ip,
		e.userID,
		e.traceID,
		e.userAgent,
	)
	for _, errMsg := range e.errors {
		log.Printf("[Error] trace_id: %s | %s", e.traceID, errMsg)
	}
	if e.rawQuery != "" {
		log.Printf("[Query] %s?%s", e.path, e.rawQuery)
	}
}

// Logger returns a gin middleware that records each HTTP request.
// Log writes are offloaded to a background goroutine via a buffered channel,
// so they never block the handler goroutine or inflate response latency.
func Logger() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		path := c.Request.URL.Path
		raw := sanitizeQuery(c.Request.URL.RawQuery)

		c.Next()

		latency := time.Since(start)
		userID, _ := c.Get("user_id")
		traceID := GetTraceID(c)

		var errStrings []string
		for _, err := range c.Errors {
			errStrings = append(errStrings, fmt.Sprintf("%v", err))
		}

		entry := logEntry{
			ts:        time.Now(),
			method:    c.Request.Method,
			path:      path,
			rawQuery:  raw,
			status:    c.Writer.Status(),
			latency:   latency,
			ip:        c.ClientIP(),
			userAgent: c.Request.UserAgent(),
			userID:    userID,
			traceID:   traceID,
			errors:    errStrings,
		}

		// Non-blocking send: if the channel is full (extreme overload), drop the log
		// line rather than stall the HTTP response.
		select {
		case asyncLogCh <- entry:
		default:
			// Channel full — log drop is preferable to adding latency.
		}
	}
}

func sanitizeQuery(raw string) string {
	if raw == "" {
		return ""
	}

	values, err := url.ParseQuery(raw)
	if err != nil {
		return ""
	}

	for key := range values {
		if isSensitiveQueryKey(key) {
			values.Set(key, "***")
		}
	}

	return values.Encode()
}

func isSensitiveQueryKey(key string) bool {
	normalized := strings.ToLower(strings.TrimSpace(key))
	if _, ok := sensitiveQueryKeys[normalized]; ok {
		return true
	}
	return strings.Contains(normalized, "token") || strings.Contains(normalized, "secret")
}
