package middleware

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

	"github.com/gin-gonic/gin"
	"github.com/rose_cat707/luminary/internal/model"
	"github.com/rose_cat707/luminary/internal/store/cache"
)

func IPFilter(c *cache.Store, scope model.IPRuleScope) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		rules := c.ListIPRules()
		if len(rules) == 0 {
			ctx.Next()
			return
		}
		clientIP := clientIP(ctx)
		ip := net.ParseIP(clientIP)
		if ip == nil {
			ctx.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "invalid client ip"})
			return
		}
		var allowRules, denyRules []model.IPRule
		for _, r := range rules {
			if !r.Enabled {
				continue
			}
			if r.Scope != scope && r.Scope != model.IPScopeAll {
				continue
			}
			if r.Type == model.IPRuleAllow {
				allowRules = append(allowRules, r)
			} else {
				denyRules = append(denyRules, r)
			}
		}
		for _, r := range denyRules {
			if ipMatchesRule(r.CIDR, ip) {
				ctx.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "ip denied"})
				return
			}
		}
		if len(allowRules) > 0 {
			allowed := false
			for _, r := range allowRules {
				if ipMatchesRule(r.CIDR, ip) {
					allowed = true
					break
				}
			}
			if !allowed {
				ctx.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "ip not allowed"})
				return
			}
		}
		ctx.Next()
	}
}

func cidrContains(cidr string, ip net.IP) bool {
	if !strings.Contains(cidr, "/") {
		return ip.String() == cidr
	}
	_, network, err := net.ParseCIDR(cidr)
	if err != nil {
		return false
	}
	return network.Contains(ip)
}

// ipMatchesRule checks CIDR/IP match, treating IPv4/IPv6 loopback as equivalent.
func ipMatchesRule(cidr string, ip net.IP) bool {
	if cidrContains(cidr, ip) {
		return true
	}
	if !ip.IsLoopback() {
		return false
	}
	alt := loopbackAlt(ip)
	return alt != nil && cidrContains(cidr, alt)
}

func loopbackAlt(ip net.IP) net.IP {
	if v4 := ip.To4(); v4 != nil && v4.IsLoopback() {
		return net.ParseIP("::1")
	}
	if ip.Equal(net.ParseIP("::1")) {
		return net.ParseIP("127.0.0.1")
	}
	return nil
}

func clientIP(ctx *gin.Context) string {
	return ClientIP(ctx)
}

const sessionCookie = "luminary_session"

type SessionValidator func(token string) (bool, time.Time)

func AdminAuth(validate SessionValidator) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		token := extractToken(ctx)
		if token == "" {
			ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
			return
		}
		ok, expires := validate(token)
		if !ok || time.Now().After(expires) {
			ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "session expired"})
			return
		}
		ctx.Set("session_token", token)
		ctx.Next()
	}
}

func extractToken(ctx *gin.Context) string {
	if auth := ctx.GetHeader("Authorization"); strings.HasPrefix(auth, "Bearer ") {
		return strings.TrimPrefix(auth, "Bearer ")
	}
	if cookie, err := ctx.Cookie(sessionCookie); err == nil {
		return cookie
	}
	return ""
}

func SessionCookieName() string { return sessionCookie }

func IngressAuth(gw IngressKeyResolver) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		key := extractIngressKey(ctx)
		if key == "" {
			ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing api key"})
			return
		}
		ingress, err := gw.ResolveIngressKey(key)
		if err != nil {
			ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid api key"})
			return
		}
		ctx.Set("ingress_key", ingress)
		ctx.Next()
	}
}

func extractIngressKey(ctx *gin.Context) string {
	if auth := ctx.GetHeader("Authorization"); strings.HasPrefix(auth, "Bearer ") {
		return strings.TrimPrefix(auth, "Bearer ")
	}
	return ctx.GetHeader("x-api-key")
}

type IngressKeyResolver interface {
	ResolveIngressKey(key string) (*model.IngressKey, error)
}

// RequireIngressProtocol restricts routes to ingress keys of the given protocol family.
func RequireIngressProtocol(want model.IngressKeyProtocol) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		v, ok := ctx.Get("ingress_key")
		if !ok {
			ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing api key"})
			return
		}
		k, ok := v.(*model.IngressKey)
		if !ok {
			ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid api key"})
			return
		}
		if k.EffectiveProtocol() != want {
			abortWrongProtocol(ctx, want, protocolMismatchMessage(k.EffectiveProtocol(), want))
			return
		}
		ctx.Next()
	}
}

func protocolMismatchMessage(got, want model.IngressKeyProtocol) string {
	if want == model.IngressProtocolImage {
		return "this api key is for text (OpenAI) API only; use an image (Replicate) ingress key"
	}
	return "this api key is for image (Replicate) API only; use a text (OpenAI) ingress key"
}

func abortWrongProtocol(ctx *gin.Context, want model.IngressKeyProtocol, msg string) {
	if want == model.IngressProtocolImage {
		ctx.AbortWithStatusJSON(http.StatusForbidden, gin.H{"detail": msg})
		return
	}
	ctx.AbortWithStatusJSON(http.StatusForbidden, gin.H{
		"error": gin.H{"message": msg, "type": "forbidden"},
	})
}

func Logger() gin.HandlerFunc {
	return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
		return param.TimeStamp.Format(time.RFC3339) + " " + param.Method + " " + param.Path + " " + param.ClientIP + "\n"
	})
}
