package handlers

import (
	"fmt"
	"strings"
	"tinyauth/internal/types"
	"tinyauth/internal/utils"

	"github.com/gin-gonic/gin"
	"github.com/pquerna/otp/totp"
	"github.com/rs/zerolog/log"
)

func (h *Handlers) LoginHandler(c *gin.Context) {
	var login types.LoginRequest

	err := c.BindJSON(&login)
	if err != nil {
		log.Error().Err(err).Msg("Failed to bind JSON")
		c.JSON(400, gin.H{
			"status":  400,
			"message": "Bad Request",
		})
		return
	}

	log.Debug().Msg("Got login request")

	clientIP := c.ClientIP()

	// Create an identifier for rate limiting (username or IP if username doesn't exist yet)
	rateIdentifier := login.Username
	if rateIdentifier == "" {
		rateIdentifier = clientIP
	}

	// Check if the account is locked due to too many failed attempts
	locked, remainingTime := h.Auth.IsAccountLocked(rateIdentifier)
	if locked {
		log.Warn().Str("identifier", rateIdentifier).Int("remaining_seconds", remainingTime).Msg("Account is locked due to too many failed login attempts")
		c.JSON(429, gin.H{
			"status":  429,
			"message": fmt.Sprintf("Too many failed login attempts. Try again in %d seconds", remainingTime),
		})
		return
	}

	// Search for a user based on username
	log.Debug().Interface("username", login.Username).Msg("Searching for user")

	userSearch := h.Auth.SearchUser(login.Username)

	// User does not exist
	if userSearch.Type == "" {
		log.Debug().Str("username", login.Username).Msg("User not found")
		// Record failed login attempt
		h.Auth.RecordLoginAttempt(rateIdentifier, false)
		c.JSON(401, gin.H{
			"status":  401,
			"message": "Unauthorized",
		})
		return
	}

	log.Debug().Msg("Got user")

	// Check if password is correct
	if !h.Auth.VerifyUser(userSearch, login.Password) {
		log.Debug().Str("username", login.Username).Msg("Password incorrect")
		// Record failed login attempt
		h.Auth.RecordLoginAttempt(rateIdentifier, false)
		c.JSON(401, gin.H{
			"status":  401,
			"message": "Unauthorized",
		})
		return
	}

	log.Debug().Msg("Password correct, checking totp")

	// Record successful login attempt (will reset failed attempt counter)
	h.Auth.RecordLoginAttempt(rateIdentifier, true)

	// Check if user is using TOTP
	if userSearch.Type == "local" {
		// Get local user
		localUser := h.Auth.GetLocalUser(login.Username)

		// Check if TOTP is enabled
		if localUser.TotpSecret != "" {
			log.Debug().Msg("Totp enabled")

			// Set totp pending cookie
			h.Auth.CreateSessionCookie(c, &types.SessionCookie{
				Username:    login.Username,
				Name:        utils.Capitalize(login.Username),
				Email:       fmt.Sprintf("%s@%s", strings.ToLower(login.Username), h.Config.Domain),
				Provider:    "username",
				TotpPending: true,
			})

			// Return totp required
			c.JSON(200, gin.H{
				"status":      200,
				"message":     "Waiting for totp",
				"totpPending": true,
			})
			return
		}
	}

	// Create session cookie with username as provider
	h.Auth.CreateSessionCookie(c, &types.SessionCookie{
		Username: login.Username,
		Name:     utils.Capitalize(login.Username),
		Email:    fmt.Sprintf("%s@%s", strings.ToLower(login.Username), h.Config.Domain),
		Provider: "username",
	})

	// Return logged in
	c.JSON(200, gin.H{
		"status":      200,
		"message":     "Logged in",
		"totpPending": false,
	})
}

func (h *Handlers) TOTPHandler(c *gin.Context) {
	var totpReq types.TotpRequest

	err := c.BindJSON(&totpReq)
	if err != nil {
		log.Error().Err(err).Msg("Failed to bind JSON")
		c.JSON(400, gin.H{
			"status":  400,
			"message": "Bad Request",
		})
		return
	}

	log.Debug().Msg("Checking totp")

	// Get user context
	userContext := h.Hooks.UseUserContext(c)

	// Check if we have a user
	if userContext.Username == "" {
		log.Debug().Msg("No user context")
		c.JSON(401, gin.H{
			"status":  401,
			"message": "Unauthorized",
		})
		return
	}

	// Get user
	user := h.Auth.GetLocalUser(userContext.Username)

	// Check if totp is correct
	ok := totp.Validate(totpReq.Code, user.TotpSecret)

	if !ok {
		log.Debug().Msg("Totp incorrect")
		c.JSON(401, gin.H{
			"status":  401,
			"message": "Unauthorized",
		})
		return
	}

	log.Debug().Msg("Totp correct")

	// Create session cookie with username as provider
	h.Auth.CreateSessionCookie(c, &types.SessionCookie{
		Username: user.Username,
		Name:     utils.Capitalize(user.Username),
		Email:    fmt.Sprintf("%s@%s", strings.ToLower(user.Username), h.Config.Domain),
		Provider: "username",
	})

	// Return logged in
	c.JSON(200, gin.H{
		"status":  200,
		"message": "Logged in",
	})
}

func (h *Handlers) LogoutHandler(c *gin.Context) {
	log.Debug().Msg("Cleaning up redirect cookie")

	h.Auth.DeleteSessionCookie(c)

	c.JSON(200, gin.H{
		"status":  200,
		"message": "Logged out",
	})
}
