package api

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

	"github.com/gin-gonic/gin"
	"golang.org/x/crypto/bcrypt"

	"github.com/prism/proxy/internal/auth"
)

type loginReq struct {
	Password string `json:"password"`
}

func (s *Server) authMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		settings, err := s.app.Store.GetSettings(c.Request.Context())
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			c.Abort()
			return
		}
		if !s.authorizeRequest(c, settings) {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
			c.Abort()
			return
		}
		c.Next()
	}
}

func (s *Server) authStatus(c *gin.Context) {
	settings, err := s.app.Store.GetSettings(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"enabled": settings["auth_enabled"] == "true"})
}

func (s *Server) login(c *gin.Context) {
	var req loginReq
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	ctx := c.Request.Context()
	settings, err := s.app.Store.GetSettings(ctx)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	ip := clientIP(c)
	cfg := auth.ParseLoginConfig(settings)
	check, err := s.loginLimiter.Check(ctx, ip, cfg)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	if !check.Allowed {
		writeLoginLocked(c, check.RetryAfter)
		return
	}

	hash := settings["admin_password"]
	if !checkPassword(req.Password, hash) {
		time.Sleep(loginFailureDelay())
		result, err := s.loginLimiter.RecordFailure(ctx, ip, cfg)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		if result.Locked {
			writeLoginLocked(c, result.RetryAfter)
			return
		}
		c.JSON(http.StatusUnauthorized, gin.H{
			"error":     "invalid password",
			"remaining": result.Remaining,
		})
		return
	}
	if err := s.loginLimiter.RecordSuccess(ctx, ip); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	token := issueToken(hash, 24*time.Hour)
	c.JSON(http.StatusOK, gin.H{"token": token})
}

func writeLoginLocked(c *gin.Context, retryAfter time.Duration) {
	secs := int(retryAfter.Seconds())
	if secs < 1 {
		secs = 1
	}
	c.JSON(http.StatusTooManyRequests, gin.H{
		"error":       "too many login attempts",
		"retry_after": secs,
	})
}

func loginFailureDelay() time.Duration {
	return 300*time.Millisecond + time.Duration(rand.Intn(200))*time.Millisecond
}

func checkPassword(password, stored string) bool {
	if strings.HasPrefix(stored, "$2a$") {
		return bcrypt.CompareHashAndPassword([]byte(stored), []byte(password)) == nil
	}
	return password == stored
}
