package middleware

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"log"
	"time"

	"github.com/gin-gonic/gin"

	"github.com/user-management-system/internal/domain"
	"github.com/user-management-system/internal/repository"
)

type OperationLogMiddleware struct {
	repo *repository.OperationLogRepository
}

func NewOperationLogMiddleware(repo *repository.OperationLogRepository) *OperationLogMiddleware {
	return &OperationLogMiddleware{repo: repo}
}

type bodyWriter struct {
	gin.ResponseWriter
	statusCode int
}

func newBodyWriter(w gin.ResponseWriter) *bodyWriter {
	return &bodyWriter{ResponseWriter: w, statusCode: 200}
}

func (bw *bodyWriter) WriteHeader(code int) {
	bw.statusCode = code
	bw.ResponseWriter.WriteHeader(code)
}

func (bw *bodyWriter) WriteHeaderNow() {
	bw.ResponseWriter.WriteHeaderNow()
}

func (m *OperationLogMiddleware) Record() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method
		if method == "GET" || method == "HEAD" || method == "OPTIONS" {
			c.Next()
			return
		}

		var reqParams string
		if c.Request.Body != nil {
			bodyBytes, err := io.ReadAll(io.LimitReader(c.Request.Body, 4096))
			if err == nil {
				c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
				reqParams = sanitizeParams(bodyBytes)
			}
		}

		bw := newBodyWriter(c.Writer)
		c.Writer = bw

		c.Next()

		var userIDPtr *int64
		if uid, exists := c.Get("user_id"); exists {
			if id, ok := uid.(int64); ok {
				userID := id
				userIDPtr = &userID
			}
		}

		isAdmin := IsAdmin(c)
		opType := methodToType(method)
		if isAdmin {
			opType = "admin:" + opType
		}

		logEntry := &domain.OperationLog{
			UserID:         userIDPtr,
			OperationType:  opType,
			OperationName:  c.FullPath(),
			RequestMethod:  method,
			RequestPath:    c.Request.URL.Path,
			RequestParams:  reqParams,
			ResponseStatus: bw.statusCode,
			IP:             c.ClientIP(),
			UserAgent:      c.Request.UserAgent(),
		}

		if m == nil || m.repo == nil {
			return
		}

		go func(entry *domain.OperationLog) {
			ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
			defer cancel()
			if err := m.repo.Create(ctx, entry); err != nil {
				log.Printf("[operation-log] create failed: %v", err)
			}
		}(logEntry)
	}
}

func methodToType(method string) string {
	switch method {
	case "POST":
		return "CREATE"
	case "PUT", "PATCH":
		return "UPDATE"
	case "DELETE":
		return "DELETE"
	default:
		return "OTHER"
	}
}

func sanitizeParams(data []byte) string {
	var payload map[string]interface{}
	if err := json.Unmarshal(data, &payload); err != nil {
		if len(data) > 500 {
			return string(data[:500]) + "..."
		}
		return string(data)
	}

	for _, field := range []string{"password", "old_password", "new_password", "confirm_password", "secret", "token"} {
		if _, ok := payload[field]; ok {
			payload[field] = "***"
		}
	}

	result, err := json.Marshal(payload)
	if err != nil {
		return ""
	}
	return string(result)
}
