// internal/middleware/idempotency.go
package middleware

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"lost-and-found/internal/utils"
	"net/http"
	"sync"
	"time"

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

// ✅ KRITERIA BACKEND: Idempotency (5 Poin) - Advanced Feature

// IdempotencyStore menyimpan hasil request yang sudah diproses
type IdempotencyStore struct {
	mu      sync.RWMutex
	results map[string]*IdempotencyResult
}

// IdempotencyResult menyimpan response dari request sebelumnya
type IdempotencyResult struct {
	StatusCode int
	Body       interface{}
	Timestamp  time.Time
}

var idempotencyStore = &IdempotencyStore{
	results: make(map[string]*IdempotencyResult),
}

// cleanupIdempotencyStore membersihkan hasil yang sudah lama (> 24 jam)
func cleanupIdempotencyStore() {
	ticker := time.NewTicker(1 * time.Hour)
	go func() {
		for range ticker.C {
			idempotencyStore.mu.Lock()
			now := time.Now()
			for key, result := range idempotencyStore.results {
				if now.Sub(result.Timestamp) > 24*time.Hour {
					delete(idempotencyStore.results, key)
				}
			}
			idempotencyStore.mu.Unlock()
		}
	}()
}

func init() {
	cleanupIdempotencyStore()
}

// IdempotencyMiddleware mencegah double-submit pada endpoint kritis
// Endpoint kritis: Payment, Create Order, Transfer, Submit Claim
func IdempotencyMiddleware() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		// Hanya apply untuk POST/PUT/PATCH methods
		if ctx.Request.Method != http.MethodPost &&
			ctx.Request.Method != http.MethodPut &&
			ctx.Request.Method != http.MethodPatch {
			ctx.Next()
			return
		}

		// Get idempotency key from header
		idempotencyKey := ctx.GetHeader("Idempotency-Key")

		// Jika tidak ada idempotency key, skip (tidak wajib untuk semua request)
		if idempotencyKey == "" {
			ctx.Next()
			return
		}

		// Generate unique key: idempotency-key + user-id + path + method
		userID, _ := ctx.Get("user_id")
		uniqueKey := generateUniqueKey(idempotencyKey, fmt.Sprintf("%v", userID), ctx.Request.URL.Path, ctx.Request.Method)

		// Check if request sudah pernah diproses
		idempotencyStore.mu.RLock()
		result, exists := idempotencyStore.results[uniqueKey]
		idempotencyStore.mu.RUnlock()

		if exists {
			// Request sudah pernah diproses, return hasil sebelumnya
			ctx.JSON(result.StatusCode, gin.H{
				"success":     true,
				"message":     "Request already processed (idempotent)",
				"data":        result.Body,
				"idempotent":  true,
				"original_at": result.Timestamp,
			})
			ctx.Abort()
			return
		}

		// Mark request sebagai "processing" untuk prevent concurrent duplicate
		processingLock := fmt.Sprintf("%s-processing", uniqueKey)
		idempotencyStore.mu.Lock()
		if _, processing := idempotencyStore.results[processingLock]; processing {
			idempotencyStore.mu.Unlock()
			utils.ErrorResponse(ctx, http.StatusConflict, "Request is being processed", "Duplicate request detected")
			ctx.Abort()
			return
		}
		// Lock dengan timestamp sebagai marker
		idempotencyStore.results[processingLock] = &IdempotencyResult{
			Timestamp: time.Now(),
		}
		idempotencyStore.mu.Unlock()

		// Custom response writer untuk capture hasil
		blw := &bodyLogWriter{body: []byte{}, ResponseWriter: ctx.Writer}
		ctx.Writer = blw

		// Process request
		ctx.Next()

		// Remove processing lock
		idempotencyStore.mu.Lock()
		delete(idempotencyStore.results, processingLock)
		idempotencyStore.mu.Unlock()

		// Simpan hasil hanya jika sukses (2xx status)
		if ctx.Writer.Status() >= 200 && ctx.Writer.Status() < 300 {
			idempotencyStore.mu.Lock()
			idempotencyStore.results[uniqueKey] = &IdempotencyResult{
				StatusCode: ctx.Writer.Status(),
				Body:       blw.body,
				Timestamp:  time.Now(),
			}
			idempotencyStore.mu.Unlock()
		}
	}
}

// bodyLogWriter untuk capture response body
type bodyLogWriter struct {
	gin.ResponseWriter
	body []byte
}

func (w *bodyLogWriter) Write(b []byte) (int, error) {
	w.body = append(w.body, b...)
	return w.ResponseWriter.Write(b)
}

// generateUniqueKey membuat unique key dari kombinasi parameter
func generateUniqueKey(idempotencyKey, userID, path, method string) string {
	data := fmt.Sprintf("%s:%s:%s:%s", idempotencyKey, userID, path, method)
	hash := sha256.Sum256([]byte(data))
	return hex.EncodeToString(hash[:])
}

// ClearIdempotencyCache membersihkan cache (untuk testing/admin)
func ClearIdempotencyCache() {
	idempotencyStore.mu.Lock()
	defer idempotencyStore.mu.Unlock()
	idempotencyStore.results = make(map[string]*IdempotencyResult)
}

// GetIdempotencyCacheSize return ukuran cache (untuk monitoring)
func GetIdempotencyCacheSize() int {
	idempotencyStore.mu.RLock()
	defer idempotencyStore.mu.RUnlock()
	return len(idempotencyStore.results)
}