package middleware

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"solacenet-gateway/cache"
	"solacenet-gateway/config"
	"time"

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

type PolicyDecisionRequest struct {
	TenantID    string                 `json:"tenantId"`
	ProgramID   string                 `json:"programId,omitempty"`
	CapabilityID string                `json:"capabilityId"`
	Region      string                 `json:"region,omitempty"`
	Channel     string                 `json:"channel,omitempty"`
	Actor       string                 `json:"actor,omitempty"`
	Context     map[string]interface{} `json:"context,omitempty"`
}

type PolicyDecisionResponse struct {
	Allowed    bool                   `json:"allowed"`
	Mode       string                 `json:"mode"`
	Limits     map[string]interface{} `json:"limits,omitempty"`
	ReasonCode string                 `json:"reasonCode,omitempty"`
	DecisionID string                 `json:"decisionId"`
}

// CapabilityCheckMiddleware checks if a capability is enabled before routing
func CapabilityCheckMiddleware(cfg *config.Config, cache *cache.Cache) gin.HandlerFunc {
	return func(c *gin.Context) {
		// Extract capability ID from request path or header
		capabilityID := c.GetHeader("X-Capability-ID")
		if capabilityID == "" {
			// Try to extract from path pattern
			// This is a simplified version - adjust based on your routing
			capabilityID = extractCapabilityFromPath(c.Request.URL.Path)
		}

		if capabilityID == "" {
			c.Next()
			return
		}

		// Extract context from request
		tenantID := c.GetHeader("X-Tenant-ID")
		programID := c.GetHeader("X-Program-ID")
		region := c.GetHeader("X-Region")
		channel := c.GetHeader("X-Channel")
		actor := c.GetHeader("X-Actor")

		// Check cache first
		cacheKey := fmt.Sprintf("policy:decision:%s:%s:%s:%s:%s:%s",
			tenantID, programID, capabilityID, region, channel, actor)

		if cached, err := cache.Get(cacheKey); err == nil && cached != nil {
			var decision PolicyDecisionResponse
			if json.Unmarshal(cached, &decision) == nil {
				if !decision.Allowed {
					c.JSON(http.StatusForbidden, gin.H{
						"error":      "Capability not available",
						"reasonCode": decision.ReasonCode,
						"mode":       decision.Mode,
					})
					c.Abort()
					return
				}
				c.Set("policyDecision", decision)
				c.Next()
				return
			}
		}

		// Call policy engine
		decisionReq := PolicyDecisionRequest{
			TenantID:     tenantID,
			ProgramID:    programID,
			CapabilityID: capabilityID,
			Region:       region,
			Channel:      channel,
			Actor:        actor,
		}

		decision, err := callPolicyEngine(cfg, decisionReq)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{
				"error": "Failed to check capability",
			})
			c.Abort()
			return
		}

		// Cache the decision
		if decisionJSON, err := json.Marshal(decision); err == nil {
			cache.Set(cacheKey, decisionJSON, time.Duration(cfg.CacheTTL)*time.Second)
		}

		if !decision.Allowed {
			c.JSON(http.StatusForbidden, gin.H{
				"error":      "Capability not available",
				"reasonCode": decision.ReasonCode,
				"mode":       decision.Mode,
			})
			c.Abort()
			return
		}

		c.Set("policyDecision", decision)
		c.Next()
	}
}

func callPolicyEngine(cfg *config.Config, req PolicyDecisionRequest) (*PolicyDecisionResponse, error) {
	reqBody, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}

	resp, err := http.Post(
		fmt.Sprintf("%s/api/v1/solacenet/policy/decide", cfg.PolicyEngineURL),
		"application/json",
		bytes.NewBuffer(reqBody),
	)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var decision PolicyDecisionResponse
	if err := json.Unmarshal(body, &decision); err != nil {
		return nil, err
	}

	return &decision, nil
}

func extractCapabilityFromPath(path string) string {
	// Simplified extraction - adjust based on your routing patterns
	// Example: /api/v1/payments/... -> "payment-gateway"
	// This should be configured based on your actual routing
	return ""
}
