package middleware

import (
	"bookhoard/internal/config"
	"bufio"
	"bytes"
	"encoding/json"
	"io"
	"log"
	"net"
	"net/http"
	"time"

	"github.com/google/uuid"
	"github.com/labstack/echo/v5"
)

type responseWriter struct {
	http.ResponseWriter
	size   int
	status int
}

func (r *responseWriter) WriteHeader(statusCode int) {
	r.status = statusCode
	r.ResponseWriter.WriteHeader(statusCode)
}
func (r *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	// Pass through Hijacker if underlying writer supports it
	if hj, ok := r.ResponseWriter.(http.Hijacker); ok {
		return hj.Hijack()
	}
	return nil, nil, http.ErrNotSupported
}
func (r *responseWriter) Write(b []byte) (int, error) {
	size, err := r.ResponseWriter.Write(b)
	r.size += size
	return size, err
}

type RequestLogEntry struct {
	RequestID    string            `json:"request_id"`
	Timestamp    time.Time         `json:"timestamp"`
	Method       string            `json:"method"`
	Path         string            `json:"path"`
	QueryParams  map[string]string `json:"query_params,omitempty"`
	Headers      map[string]string `json:"headers,omitempty"`
	Body         interface{}       `json:"body,omitempty"`
	UserID       string            `json:"user_id,omitempty"`
	UserRole     string            `json:"user_role,omitempty"`
	UserEmail    string            `json:"user_email,omitempty"`
	RemoteAddr   string            `json:"remote_addr"`
	UserAgent    string            `json:"user_agent,omitempty"`
	Duration     time.Duration     `json:"duration"`
	StatusCode   int               `json:"status_code"`
	ResponseSize int64             `json:"response_size"`
	Error        string            `json:"error,omitempty"`
}

func RequestTracingMiddleware(cfg *config.Config) echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c *echo.Context) error {
			start := time.Now()

			requestID := c.Response().Header().Get(echo.HeaderXRequestID)
			if requestID == "" {
				requestID = uuid.New().String()
				c.Response().Header().Set(echo.HeaderXRequestID, requestID)
			}

			recorder := &responseWriter{
				ResponseWriter: c.Response(),
			}
			c.SetResponse(recorder)

			var body interface{}
			if c.Request().Body != nil && c.Request().Method != "GET" {
				bodyBytes, _ := io.ReadAll(c.Request().Body)
				c.Request().Body = io.NopCloser(bytes.NewBuffer(bodyBytes))

				if len(bodyBytes) > 0 && len(bodyBytes) < 10000 {
					json.Unmarshal(bodyBytes, &body)
				}
			}

			headers := make(map[string]string)
			for k, v := range c.Request().Header {
				if len(v) > 0 {
					headers[k] = v[0]
				}
			}

			userID, _ := c.Get("user_id").(string)
			userRole, _ := c.Get("user_role").(string)
			userEmail, _ := c.Get("user_email").(string)

			err := next(c)

			duration := time.Since(start)

			queryParams := make(map[string]string)
			for k, v := range c.QueryParams() {
				if len(v) > 0 {
					queryParams[k] = v[0]
				}
			}

			logEntry := RequestLogEntry{
				RequestID:    requestID,
				Timestamp:    start,
				Method:       c.Request().Method,
				Path:         c.Request().URL.Path,
				QueryParams:  queryParams,
				Headers:      headers,
				Body:         body,
				UserID:       userID,
				UserRole:     userRole,
				UserEmail:    userEmail,
				RemoteAddr:   c.RealIP(),
				UserAgent:    c.Request().UserAgent(),
				Duration:     duration,
				StatusCode:   recorder.status,
				ResponseSize: int64(recorder.size),
			}

			if err != nil {
				logEntry.Error = err.Error()
			}

			logJSON, _ := json.Marshal(logEntry)
			log.Printf("[REQUEST] %s", string(logJSON))

			return err
		}
	}
}
