package http

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

	"backupx/server/internal/apperror"
	"backupx/server/internal/service"
	"backupx/server/pkg/response"
	"github.com/gin-gonic/gin"
)

const (
	trustedDeviceCookieName   = "backupx_trusted_device"
	trustedDeviceCookiePath   = "/api/auth"
	trustedDeviceCookieMaxAge = int((30 * 24 * time.Hour) / time.Second)
)

type AuthHandler struct {
	authService *service.AuthService
}

func NewAuthHandler(authService *service.AuthService) *AuthHandler {
	return &AuthHandler{authService: authService}
}

func (h *AuthHandler) SetupStatus(c *gin.Context) {
	initialized, err := h.authService.SetupStatus(c.Request.Context())
	if err != nil {
		response.Error(c, err)
		return
	}
	response.Success(c, gin.H{"initialized": initialized})
}

func (h *AuthHandler) Setup(c *gin.Context) {
	var input service.SetupInput
	if err := c.ShouldBindJSON(&input); err != nil {
		response.Error(c, apperror.BadRequest("AUTH_SETUP_INVALID", "初始化参数不合法", err))
		return
	}
	payload, err := h.authService.Setup(c.Request.Context(), input)
	if err != nil {
		response.Error(c, err)
		return
	}
	response.Success(c, payload)
}

func (h *AuthHandler) Login(c *gin.Context) {
	var input service.LoginInput
	if err := c.ShouldBindJSON(&input); err != nil {
		response.Error(c, apperror.BadRequest("AUTH_LOGIN_INVALID", "登录参数不合法", err))
		return
	}
	if strings.TrimSpace(input.TrustedDeviceToken) == "" {
		input.TrustedDeviceToken = trustedDeviceCookieValue(c)
	}
	payload, err := h.authService.Login(c.Request.Context(), input, ClientKey(c))
	if err != nil {
		response.Error(c, err)
		return
	}
	if payload.TrustedDeviceToken != "" {
		setTrustedDeviceCookie(c, payload.TrustedDeviceToken)
		payload.TrustedDeviceToken = ""
	}
	response.Success(c, payload)
}

func (h *AuthHandler) Profile(c *gin.Context) {
	subjectValue, _ := c.Get(contextUserSubjectKey)
	subject, err := service.SubjectFromContextValue(subjectValue)
	if err != nil {
		response.Error(c, apperror.Unauthorized("AUTH_INVALID_SUBJECT", "无效登录态", err))
		return
	}
	user, err := h.authService.GetCurrentUser(c.Request.Context(), subject)
	if err != nil {
		response.Error(c, err)
		return
	}
	response.Success(c, user)
}

func (h *AuthHandler) ChangePassword(c *gin.Context) {
	subjectValue, _ := c.Get(contextUserSubjectKey)
	subject, err := service.SubjectFromContextValue(subjectValue)
	if err != nil {
		response.Error(c, apperror.Unauthorized("AUTH_INVALID_SUBJECT", "无效登录态", err))
		return
	}
	var input service.ChangePasswordInput
	if err := c.ShouldBindJSON(&input); err != nil {
		response.Error(c, apperror.BadRequest("AUTH_PASSWORD_INVALID", "参数不合法", err))
		return
	}
	if err := h.authService.ChangePassword(c.Request.Context(), subject, input); err != nil {
		response.Error(c, err)
		return
	}
	clearTrustedDeviceCookie(c)
	response.Success(c, gin.H{"changed": true})
}

func (h *AuthHandler) PrepareTwoFactor(c *gin.Context) {
	subjectValue, _ := c.Get(contextUserSubjectKey)
	subject, err := service.SubjectFromContextValue(subjectValue)
	if err != nil {
		response.Error(c, apperror.Unauthorized("AUTH_INVALID_SUBJECT", "无效登录态", err))
		return
	}
	var input service.TwoFactorSetupInput
	if err := c.ShouldBindJSON(&input); err != nil {
		response.Error(c, apperror.BadRequest("AUTH_2FA_INVALID", "参数不合法", err))
		return
	}
	payload, err := h.authService.PrepareTwoFactor(c.Request.Context(), subject, input)
	if err != nil {
		response.Error(c, err)
		return
	}
	response.Success(c, payload)
}

func (h *AuthHandler) EnableTwoFactor(c *gin.Context) {
	subjectValue, _ := c.Get(contextUserSubjectKey)
	subject, err := service.SubjectFromContextValue(subjectValue)
	if err != nil {
		response.Error(c, apperror.Unauthorized("AUTH_INVALID_SUBJECT", "无效登录态", err))
		return
	}
	var input service.EnableTwoFactorInput
	if err := c.ShouldBindJSON(&input); err != nil {
		response.Error(c, apperror.BadRequest("AUTH_2FA_INVALID", "参数不合法", err))
		return
	}
	user, err := h.authService.EnableTwoFactor(c.Request.Context(), subject, input)
	if err != nil {
		response.Error(c, err)
		return
	}
	response.Success(c, user)
}

func (h *AuthHandler) DisableTwoFactor(c *gin.Context) {
	subjectValue, _ := c.Get(contextUserSubjectKey)
	subject, err := service.SubjectFromContextValue(subjectValue)
	if err != nil {
		response.Error(c, apperror.Unauthorized("AUTH_INVALID_SUBJECT", "无效登录态", err))
		return
	}
	var input service.DisableTwoFactorInput
	if err := c.ShouldBindJSON(&input); err != nil {
		response.Error(c, apperror.BadRequest("AUTH_2FA_INVALID", "参数不合法", err))
		return
	}
	user, err := h.authService.DisableTwoFactor(c.Request.Context(), subject, input)
	if err != nil {
		response.Error(c, err)
		return
	}
	if !user.MFAEnabled {
		clearTrustedDeviceCookie(c)
	}
	response.Success(c, user)
}

func (h *AuthHandler) RegenerateRecoveryCodes(c *gin.Context) {
	subjectValue, _ := c.Get(contextUserSubjectKey)
	subject, err := service.SubjectFromContextValue(subjectValue)
	if err != nil {
		response.Error(c, apperror.Unauthorized("AUTH_INVALID_SUBJECT", "无效登录态", err))
		return
	}
	var input service.RegenerateRecoveryCodesInput
	if err := c.ShouldBindJSON(&input); err != nil {
		response.Error(c, apperror.BadRequest("AUTH_2FA_INVALID", "参数不合法", err))
		return
	}
	payload, err := h.authService.RegenerateRecoveryCodes(c.Request.Context(), subject, input)
	if err != nil {
		response.Error(c, err)
		return
	}
	response.Success(c, payload)
}

func (h *AuthHandler) ConfigureOTP(c *gin.Context) {
	subjectValue, _ := c.Get(contextUserSubjectKey)
	subject, err := service.SubjectFromContextValue(subjectValue)
	if err != nil {
		response.Error(c, apperror.Unauthorized("AUTH_INVALID_SUBJECT", "无效登录态", err))
		return
	}
	var input service.OTPConfigInput
	if err := c.ShouldBindJSON(&input); err != nil {
		response.Error(c, apperror.BadRequest("AUTH_OTP_INVALID", "参数不合法", err))
		return
	}
	user, err := h.authService.ConfigureOutOfBandOTP(c.Request.Context(), subject, input)
	if err != nil {
		response.Error(c, err)
		return
	}
	if !user.MFAEnabled {
		clearTrustedDeviceCookie(c)
	}
	response.Success(c, user)
}

func (h *AuthHandler) SendLoginOTP(c *gin.Context) {
	var input service.LoginOTPInput
	if err := c.ShouldBindJSON(&input); err != nil {
		response.Error(c, apperror.BadRequest("AUTH_OTP_INVALID", "参数不合法", err))
		return
	}
	if err := h.authService.SendLoginOTP(c.Request.Context(), input, ClientKey(c)); err != nil {
		response.Error(c, err)
		return
	}
	response.Success(c, gin.H{"sent": true})
}

func (h *AuthHandler) BeginWebAuthnRegistration(c *gin.Context) {
	subjectValue, _ := c.Get(contextUserSubjectKey)
	subject, err := service.SubjectFromContextValue(subjectValue)
	if err != nil {
		response.Error(c, apperror.Unauthorized("AUTH_INVALID_SUBJECT", "无效登录态", err))
		return
	}
	var input service.WebAuthnRegistrationOptionsInput
	if err := c.ShouldBindJSON(&input); err != nil {
		response.Error(c, apperror.BadRequest("AUTH_WEBAUTHN_INVALID", "参数不合法", err))
		return
	}
	options, err := h.authService.BeginWebAuthnRegistration(c.Request.Context(), subject, input, webAuthnRequestContext(c))
	if err != nil {
		response.Error(c, err)
		return
	}
	response.Success(c, options)
}

func (h *AuthHandler) FinishWebAuthnRegistration(c *gin.Context) {
	subjectValue, _ := c.Get(contextUserSubjectKey)
	subject, err := service.SubjectFromContextValue(subjectValue)
	if err != nil {
		response.Error(c, apperror.Unauthorized("AUTH_INVALID_SUBJECT", "无效登录态", err))
		return
	}
	var input service.WebAuthnRegistrationFinishInput
	if err := c.ShouldBindJSON(&input); err != nil {
		response.Error(c, apperror.BadRequest("AUTH_WEBAUTHN_INVALID", "参数不合法", err))
		return
	}
	user, err := h.authService.FinishWebAuthnRegistration(c.Request.Context(), subject, input)
	if err != nil {
		response.Error(c, err)
		return
	}
	response.Success(c, user)
}

func (h *AuthHandler) BeginWebAuthnLogin(c *gin.Context) {
	var input service.WebAuthnLoginOptionsInput
	if err := c.ShouldBindJSON(&input); err != nil {
		response.Error(c, apperror.BadRequest("AUTH_WEBAUTHN_INVALID", "参数不合法", err))
		return
	}
	options, err := h.authService.BeginWebAuthnLogin(c.Request.Context(), input, webAuthnRequestContext(c), ClientKey(c))
	if err != nil {
		response.Error(c, err)
		return
	}
	response.Success(c, options)
}

func (h *AuthHandler) ListWebAuthnCredentials(c *gin.Context) {
	subjectValue, _ := c.Get(contextUserSubjectKey)
	subject, err := service.SubjectFromContextValue(subjectValue)
	if err != nil {
		response.Error(c, apperror.Unauthorized("AUTH_INVALID_SUBJECT", "无效登录态", err))
		return
	}
	items, err := h.authService.ListWebAuthnCredentials(c.Request.Context(), subject)
	if err != nil {
		response.Error(c, err)
		return
	}
	response.Success(c, items)
}

func (h *AuthHandler) DeleteWebAuthnCredential(c *gin.Context) {
	subjectValue, _ := c.Get(contextUserSubjectKey)
	subject, err := service.SubjectFromContextValue(subjectValue)
	if err != nil {
		response.Error(c, apperror.Unauthorized("AUTH_INVALID_SUBJECT", "无效登录态", err))
		return
	}
	var input service.WebAuthnCredentialDeleteInput
	if err := c.ShouldBindJSON(&input); err != nil {
		response.Error(c, apperror.BadRequest("AUTH_WEBAUTHN_INVALID", "参数不合法", err))
		return
	}
	user, err := h.authService.DeleteWebAuthnCredential(c.Request.Context(), subject, c.Param("id"), input)
	if err != nil {
		response.Error(c, err)
		return
	}
	if !user.MFAEnabled {
		clearTrustedDeviceCookie(c)
	}
	response.Success(c, user)
}

func (h *AuthHandler) ListTrustedDevices(c *gin.Context) {
	subjectValue, _ := c.Get(contextUserSubjectKey)
	subject, err := service.SubjectFromContextValue(subjectValue)
	if err != nil {
		response.Error(c, apperror.Unauthorized("AUTH_INVALID_SUBJECT", "无效登录态", err))
		return
	}
	items, err := h.authService.ListTrustedDevices(c.Request.Context(), subject)
	if err != nil {
		response.Error(c, err)
		return
	}
	response.Success(c, items)
}

func (h *AuthHandler) RevokeTrustedDevice(c *gin.Context) {
	subjectValue, _ := c.Get(contextUserSubjectKey)
	subject, err := service.SubjectFromContextValue(subjectValue)
	if err != nil {
		response.Error(c, apperror.Unauthorized("AUTH_INVALID_SUBJECT", "无效登录态", err))
		return
	}
	var input service.TrustedDeviceRevokeInput
	if err := c.ShouldBindJSON(&input); err != nil {
		response.Error(c, apperror.BadRequest("AUTH_TRUSTED_DEVICE_INVALID", "参数不合法", err))
		return
	}
	if err := h.authService.RevokeTrustedDevice(c.Request.Context(), subject, c.Param("id"), input); err != nil {
		response.Error(c, err)
		return
	}
	clearTrustedDeviceCookie(c)
	response.Success(c, gin.H{"deleted": true})
}

func (h *AuthHandler) Logout(c *gin.Context) {
	response.Success(c, gin.H{"loggedOut": true})
}

func webAuthnRequestContext(c *gin.Context) service.WebAuthnRequestContext {
	host := firstForwardedValue(c.Request.Host)
	if forwardedHost := firstForwardedValue(c.GetHeader("X-Forwarded-Host")); forwardedHost != "" {
		host = forwardedHost
	}
	rpID := host
	if parsedHost, _, err := net.SplitHostPort(host); err == nil {
		rpID = parsedHost
	}
	scheme := "http"
	if c.Request.TLS != nil {
		scheme = "https"
	}
	if forwardedProto := firstForwardedValue(c.GetHeader("X-Forwarded-Proto")); forwardedProto != "" {
		scheme = forwardedProto
	}
	origin := strings.TrimSpace(c.GetHeader("Origin"))
	if origin == "" {
		origin = scheme + "://" + host
	}
	return service.WebAuthnRequestContext{RPID: rpID, Origin: origin}
}

func firstForwardedValue(value string) string {
	parts := strings.Split(value, ",")
	if len(parts) == 0 {
		return ""
	}
	return strings.TrimSpace(parts[0])
}

func trustedDeviceCookieValue(c *gin.Context) string {
	token, err := c.Cookie(trustedDeviceCookieName)
	if err != nil {
		return ""
	}
	return strings.TrimSpace(token)
}

func setTrustedDeviceCookie(c *gin.Context, token string) {
	writeTrustedDeviceCookie(c, strings.TrimSpace(token), trustedDeviceCookieMaxAge)
}

func clearTrustedDeviceCookie(c *gin.Context) {
	writeTrustedDeviceCookie(c, "", -1)
}

func writeTrustedDeviceCookie(c *gin.Context, value string, maxAge int) {
	c.SetSameSite(stdhttp.SameSiteLaxMode)
	c.SetCookie(trustedDeviceCookieName, value, maxAge, trustedDeviceCookiePath, "", requestIsSecure(c), true)
}

func requestIsSecure(c *gin.Context) bool {
	if c.Request.TLS != nil {
		return true
	}
	return strings.EqualFold(firstForwardedValue(c.GetHeader("X-Forwarded-Proto")), "https")
}
