package audit

import (
	"bytes"
	"io"
	"strings"

	"github.com/atlasos/calypso/internal/common/database"
	"github.com/atlasos/calypso/internal/common/logger"
	"github.com/gin-gonic/gin"
)

// Middleware provides audit logging functionality
type Middleware struct {
	db     *database.DB
	logger *logger.Logger
}

// NewMiddleware creates a new audit middleware
func NewMiddleware(db *database.DB, log *logger.Logger) *Middleware {
	return &Middleware{
		db:     db,
		logger: log,
	}
}

// LogRequest creates middleware that logs all mutating requests
func (m *Middleware) LogRequest() gin.HandlerFunc {
	return func(c *gin.Context) {
		// Only log mutating methods
		method := c.Request.Method
		if method == "GET" || method == "HEAD" || method == "OPTIONS" {
			c.Next()
			return
		}

		// Capture request body
		var bodyBytes []byte
		if c.Request.Body != nil {
			bodyBytes, _ = io.ReadAll(c.Request.Body)
			c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
		}

		// Process request
		c.Next()

		// Get user information
		userID, _ := c.Get("user_id")
		username, _ := c.Get("username")

		// Capture response status
		status := c.Writer.Status()

		// Log to database
		go m.logAuditEvent(
			userID,
			username,
			method,
			c.Request.URL.Path,
			c.ClientIP(),
			c.GetHeader("User-Agent"),
			bodyBytes,
			status,
		)
	}
}

// logAuditEvent logs an audit event to the database
func (m *Middleware) logAuditEvent(
	userID interface{},
	username interface{},
	method, path, ipAddress, userAgent string,
	requestBody []byte,
	responseStatus int,
) {
	var userIDStr, usernameStr string
	if userID != nil {
		userIDStr, _ = userID.(string)
	}
	if username != nil {
		usernameStr, _ = username.(string)
	}

	// Determine action and resource from path
	action, resourceType, resourceID := parsePath(path)
	// Override action with HTTP method
	action = strings.ToLower(method)

	// Truncate request body if too large
	bodyJSON := string(requestBody)
	if len(bodyJSON) > 10000 {
		bodyJSON = bodyJSON[:10000] + "... (truncated)"
	}

	query := `
		INSERT INTO audit_log (
			user_id, username, action, resource_type, resource_id,
			method, path, ip_address, user_agent,
			request_body, response_status, created_at
		) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, NOW())
	`

	var bodyJSONPtr *string
	if len(bodyJSON) > 0 {
		bodyJSONPtr = &bodyJSON
	}

	_, err := m.db.Exec(query,
		userIDStr, usernameStr, action, resourceType, resourceID,
		method, path, ipAddress, userAgent,
		bodyJSONPtr, responseStatus,
	)
	if err != nil {
		m.logger.Error("Failed to log audit event", "error", err)
	}
}

// parsePath extracts action, resource type, and resource ID from a path
func parsePath(path string) (action, resourceType, resourceID string) {
	// Example: /api/v1/iam/users/123 -> action=update, resourceType=user, resourceID=123
	if len(path) < 8 || path[:8] != "/api/v1/" {
		return "unknown", "unknown", ""
	}

	remaining := path[8:]
	parts := strings.Split(remaining, "/")
	if len(parts) == 0 {
		return "unknown", "unknown", ""
	}

	// First part is usually the resource type (e.g., "iam", "tasks")
	resourceType = parts[0]

	// Determine action from HTTP method (will be set by caller)
	action = "unknown"

	// Last part might be resource ID if it's a UUID or number
	if len(parts) > 1 {
		lastPart := parts[len(parts)-1]
		// Check if it looks like a UUID or ID
		if len(lastPart) > 10 {
			resourceID = lastPart
		}
	}

	return action, resourceType, resourceID
}

