package middleware

import (
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"security-center-backend/internal/model"
	"security-center-backend/internal/service/audit"
	"security-center-backend/internal/service/jwt"
)

var skipAuditPaths = []string{
	"/health",
	"/metrics",
	"/version",
	"/api/v1/auth", // Skip all auth-related endpoints (login, callbacks, etc.)
}

func AuditTrail(auditSvc *audit.Service) gin.HandlerFunc {
	if auditSvc == nil {
		return func(c *gin.Context) { c.Next() }
	}
	return func(c *gin.Context) {
		start := time.Now()
		c.Next()

		fullPath := c.FullPath()
		if fullPath == "" {
			fullPath = c.Request.URL.Path
		}
		path := fullPath
		if shouldSkip(path) {
			return
		}
		claims := currentUserClaims(c)
		resourceType, resourceID := deriveResource(path, c.Params)

		entry := audit.Entry{
			ActorType:    "system",
			Action:       c.Request.Method,
			ResourceType: resourceType,
			ResourceID:   resourceID,
			Metadata: model.JSONB{
				"path":       c.Request.URL.Path,
				"route":      fullPath,
				"method":     c.Request.Method,
				"status":     c.Writer.Status(),
				"latency_ms": time.Since(start).Milliseconds(),
				"query":      c.Request.URL.RawQuery,
			},
			IPAddress: c.ClientIP(),
			UserAgent: c.GetHeader("User-Agent"),
		}
		applyActorAttribution(&entry, claims, c.GetString("auth_method"))
		// Log the audit entry - this will trigger the async persistence
		auditSvc.Log(c.Request.Context(), entry)
	}
}

// applyActorAttribution fills the actor-related fields of the audit entry
// based on the authenticated claims and the auth method ("", "jwt", "agent", ...).
// claims == nil keeps the default "system" attribution.
func applyActorAttribution(entry *audit.Entry, claims *jwt.Claims, authMethod string) {
	if claims == nil {
		return
	}
	if claims.ActorType == "agent" {
		entry.ActorType = "agent"
	} else {
		entry.ActorType = "user"
	}
	if claims.UserID != 0 {
		entry.UserID = &claims.UserID
		entry.ActorUserID = &claims.UserID
	}
	if claims.Email != "" {
		entry.ActorEmail = claims.Email
		entry.Metadata["user_email"] = claims.Email
	}
	if claims.ActorType == "agent" {
		entry.Metadata["agent_id"] = claims.AgentID
		entry.Metadata["agent_name"] = claims.AgentName
		entry.Metadata["delegated_user_id"] = claims.DelegatedUserID
		entry.Metadata["auth_method"] = authMethod
	}
}

func shouldSkip(path string) bool {
	if path == "" {
		return false
	}
	for _, candidate := range skipAuditPaths {
		if strings.HasPrefix(path, candidate) {
			return true
		}
	}
	return false
}

func deriveResource(path string, params gin.Params) (string, string) {
	if path == "" {
		path = "/"
	}
	segments := strings.Split(strings.Trim(path, "/"), "/")
	resource := "api"
	resourceID := ""

	if len(segments) >= 3 && segments[0] == "api" && segments[1] == "v1" {
		resource = segments[2]
		if len(segments) >= 4 && isNumeric(segments[3]) {
			resourceID = segments[3]
		}
	} else if len(segments) >= 1 && segments[0] != "" {
		resource = segments[0]
		if len(segments) >= 2 && isNumeric(segments[1]) {
			resourceID = segments[1]
		}
	}
	if val := params.ByName("id"); val != "" {
		resourceID = val
	}
	return resource, resourceID
}

func currentUserClaims(c *gin.Context) *jwt.Claims {
	claimsValue, exists := c.Get("current_user")
	if !exists {
		return nil
	}
	claims, ok := claimsValue.(*jwt.Claims)
	if !ok {
		return nil
	}
	return claims
}

func isNumeric(val string) bool {
	if val == "" {
		return false
	}
	_, err := strconv.ParseUint(val, 10, 64)
	return err == nil
}
