package middleware

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

	"hyapp-admin-server/internal/model"

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

const contextAuditLogged = "auditLogged"

type AuditStore interface {
	CreateOperationLog(log model.OperationLog) error
}

func Audit(store AuditStore) gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		c.Next()

		if store == nil || c.GetBool(contextAuditLogged) || !shouldAudit(c.Request.Method) {
			return
		}

		status := "success"
		if c.Writer.Status() >= http.StatusBadRequest {
			status = "failed"
		}
		path := c.FullPath()
		if path == "" {
			path = c.Request.URL.Path
		}
		_ = store.CreateOperationLog(model.OperationLog{
			UserID:     CurrentUserID(c),
			RequestID:  CurrentRequestID(c),
			Username:   CurrentUsername(c),
			Action:     strings.ToLower(c.Request.Method) + " " + path,
			Resource:   auditResource(path),
			ResourceID: auditResourceID(c),
			Method:     c.Request.Method,
			Path:       c.Request.URL.Path,
			IP:         c.ClientIP(),
			UserAgent:  c.Request.UserAgent(),
			Status:     status,
			HTTPStatus: c.Writer.Status(),
			RiskLevel:  auditRisk(c.Request.Method, path),
			LatencyMS:  time.Since(start).Milliseconds(),
			Detail:     fmt.Sprintf("http_status=%d", c.Writer.Status()),
		})
	}
}

func auditResourceID(c *gin.Context) string {
	if id := c.Param("id"); id != "" {
		return id
	}
	return ""
}

func auditRisk(method string, path string) string {
	if method == http.MethodDelete || strings.Contains(path, "reset-password") || strings.Contains(path, "permissions") || strings.Contains(path, "data-scopes") {
		return "high"
	}
	if shouldAudit(method) {
		return "normal"
	}
	return "low"
}

func MarkAuditLogged(c *gin.Context) {
	c.Set(contextAuditLogged, true)
}

func shouldAudit(method string) bool {
	switch method {
	case http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete:
		return true
	default:
		return false
	}
}

func auditResource(path string) string {
	parts := strings.SplitSeq(strings.Trim(path, "/"), "/")
	for part := range parts {
		if part != "" && !strings.HasPrefix(part, ":") && part != "api" && part != "v1" {
			return part
		}
	}
	return "unknown"
}
