package middleware

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"strings"
	"time"
)

// AuditRecorder is the interface that the audit middleware uses to record API calls.
// This avoids importing the service package directly, maintaining dependency inversion.
type AuditRecorder interface {
	RecordAPICall(ctx context.Context, method, path, actor string, bodyHash string, status int, latencyMs int64) error
}

// AuditConfig holds configuration for the API audit logging middleware.
type AuditConfig struct {
	// ExcludePaths are path prefixes to skip audit logging (e.g., "/health", "/ready").
	ExcludePaths []string
	// Logger for audit middleware errors (audit recording failures shouldn't break requests).
	Logger *slog.Logger
}

// NewAuditLog creates a middleware that records every API call to the audit trail.
// It captures method, path, authenticated actor, request body hash, response status, and latency.
// Audit recording is best-effort — failures are logged but don't affect the HTTP response.
func NewAuditLog(recorder AuditRecorder, cfg AuditConfig) func(http.Handler) http.Handler {
	excludeSet := make(map[string]bool, len(cfg.ExcludePaths))
	for _, p := range cfg.ExcludePaths {
		excludeSet[p] = true
	}

	logger := cfg.Logger
	if logger == nil {
		logger = slog.Default()
	}

	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// Skip excluded paths (health, readiness probes)
			for prefix := range excludeSet {
				if strings.HasPrefix(r.URL.Path, prefix) {
					next.ServeHTTP(w, r)
					return
				}
			}

			start := time.Now()

			// Hash request body for audit (don't store raw bodies — security + size concerns)
			bodyHash := ""
			if r.Body != nil && r.Body != http.NoBody {
				hasher := sha256.New()
				body, err := io.ReadAll(r.Body)
				if err == nil && len(body) > 0 {
					hasher.Write(body)
					bodyHash = hex.EncodeToString(hasher.Sum(nil))[:16] // truncated hash
					// Restore the body for downstream handlers
					r.Body = io.NopCloser(strings.NewReader(string(body)))
				}
			}

			// Extract actor from auth context
			actor := "anonymous"
			if user, ok := GetUser(r.Context()); ok && user != "" {
				actor = user
			}

			// Wrap response writer to capture status code
			wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}

			next.ServeHTTP(wrapped, r)

			latency := time.Since(start).Milliseconds()

			// Record audit event asynchronously (best-effort, don't block response).
			// SECURITY: We intentionally use r.URL.Path (not r.URL.String() or r.RequestURI)
			// to prevent query parameters from being recorded in the immutable audit trail.
			// Query strings may contain cursor tokens, API keys passed as params, or other
			// sensitive filter values. Since the audit trail is append-only with no deletion
			// capability, any sensitive data recorded would persist permanently.
			go func() {
				if err := recorder.RecordAPICall(
					context.Background(),
					r.Method,
					r.URL.Path,
					actor,
					bodyHash,
					wrapped.statusCode,
					latency,
				); err != nil {
					logger.Error("failed to record API audit event",
						"error", err,
						"method", r.Method,
						"path", r.URL.Path,
					)
				}
			}()
		})
	}
}

// AuditServiceAdapter adapts the AuditService to the AuditRecorder interface.
// This keeps the middleware decoupled from the service package.
type AuditServiceAdapter struct {
	recordFn func(ctx context.Context, actor string, actorType string, action string, resourceType string, resourceID string, details map[string]interface{}) error
}

// NewAuditServiceAdapter creates an adapter that bridges the middleware AuditRecorder
// interface to the service layer's RecordEvent method.
func NewAuditServiceAdapter(recordFn func(ctx context.Context, actor string, actorType string, action string, resourceType string, resourceID string, details map[string]interface{}) error) *AuditServiceAdapter {
	return &AuditServiceAdapter{recordFn: recordFn}
}

// RecordAPICall implements AuditRecorder by translating API call data into an audit event.
func (a *AuditServiceAdapter) RecordAPICall(ctx context.Context, method, path, actor string, bodyHash string, status int, latencyMs int64) error {
	details := map[string]interface{}{
		"method":      method,
		"path":        path,
		"body_hash":   bodyHash,
		"status":      status,
		"latency_ms":  latencyMs,
	}

	action := fmt.Sprintf("api_%s", strings.ToLower(method))
	return a.recordFn(ctx, actor, "User", action, "api", path, details)
}
