package public

import (
	"log"
	"strconv"
	"time"

	"git.ma-al.com/goc_daniel/b2b/app/config"
	"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware"
	"git.ma-al.com/goc_daniel/b2b/app/model"
	"git.ma-al.com/goc_daniel/b2b/app/service/authService"
	constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
	"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
	"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
	"git.ma-al.com/goc_daniel/b2b/app/utils/response"
	"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"

	"github.com/gofiber/fiber/v3"
)

// AuthHandler handles authentication endpoints
type AuthHandler struct {
	authService *authService.AuthService
	config      *config.Config
}

// NewAuthHandler creates a new AuthHandler instance
func NewAuthHandler() *AuthHandler {
	authService := authService.NewAuthService()
	return &AuthHandler{
		authService: authService,
		config:      config.Get(),
	}
}

// AuthHandlerRoutes registers all auth routes
func AuthHandlerRoutes(r fiber.Router) fiber.Router {
	handler := NewAuthHandler()

	r.Post("/login", handler.Login)
	r.Post("/register", handler.Register)
	r.Post("/complete-registration", handler.CompleteRegistration)
	r.Post("/forgot-password", handler.ForgotPassword)
	r.Post("/reset-password", handler.ResetPassword)
	r.Post("/logout", handler.Logout)
	r.Post("/refresh", handler.RefreshToken)

	// Google OAuth2
	r.Get("/google", handler.GoogleLogin)
	r.Get("/google/callback", handler.GoogleCallback)

	authProtected := r.Group("", middleware.AuthMiddleware())
	authProtected.Get("/me", handler.Me)
	authProtected.Post("/update-choice", handler.UpdateJWTToken)

	return r
}

func (h *AuthHandler) Login(c fiber.Ctx) error {
	var req model.LoginRequest

	if err := c.Bind().Body(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody),
		})
	}

	// Validate required fields
	if req.Email == "" || req.Password == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, responseErrors.ErrEmailPasswordRequired),
		})
	}

	// Attempt login
	response, rawRefreshToken, err := h.authService.Login(&req)
	if err != nil {
		return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, err),
		})
	}

	// Set cookies for web-based authentication
	h.setAuthCookies(c, response.AccessToken, rawRefreshToken)

	return c.JSON(response)
}

// setAuthCookies sets the access token (HTTPOnly) and refresh token (HTTPOnly) cookies,
// plus a non-HTTPOnly is_authenticated flag cookie for frontend state detection.
func (h *AuthHandler) setAuthCookies(c fiber.Ctx, accessToken, rawRefreshToken string) {
	isProduction := h.config.App.Environment == "production"

	// HTTPOnly access token cookie — not readable by JS, protects against XSS
	c.Cookie(&fiber.Cookie{
		Name:     "access_token",
		Value:    accessToken,
		Expires:  time.Now().Add(time.Duration(h.config.Auth.JWTExpiration) * time.Second),
		HTTPOnly: true,
		Secure:   isProduction,
		SameSite: "Lax",
	})

	// HTTPOnly refresh token cookie — opaque, stored as hash in DB
	if rawRefreshToken != "" {
		c.Cookie(&fiber.Cookie{
			Name:     "refresh_token",
			Value:    rawRefreshToken,
			Expires:  time.Now().Add(time.Duration(h.config.Auth.RefreshExpiration) * time.Second),
			HTTPOnly: true,
			Secure:   isProduction,
			SameSite: "Lax",
		})
	}

	// Non-HTTPOnly flag cookie — readable by JS to detect auth state.
	// Contains no sensitive data; actual auth is enforced by the HTTPOnly access_token cookie.
	c.Cookie(&fiber.Cookie{
		Name:     "is_authenticated",
		Value:    "1",
		Expires:  time.Now().Add(time.Duration(h.config.Auth.JWTExpiration) * time.Second),
		HTTPOnly: false,
		Secure:   isProduction,
		SameSite: "Lax",
	})
}

// clearAuthCookies expires all auth-related cookies
func (h *AuthHandler) clearAuthCookies(c fiber.Ctx) {
	isProduction := h.config.App.Environment == "production"
	past := time.Now().Add(-time.Hour)

	c.Cookie(&fiber.Cookie{
		Name:     "access_token",
		Value:    "",
		Expires:  past,
		HTTPOnly: true,
		Secure:   isProduction,
		SameSite: "Lax",
	})
	c.Cookie(&fiber.Cookie{
		Name:     "refresh_token",
		Value:    "",
		Expires:  past,
		HTTPOnly: true,
		Secure:   isProduction,
		SameSite: "Lax",
	})
	c.Cookie(&fiber.Cookie{
		Name:     "is_authenticated",
		Value:    "",
		Expires:  past,
		HTTPOnly: false,
		Secure:   isProduction,
		SameSite: "Lax",
	})
}

// ForgotPassword handles password reset request
func (h *AuthHandler) ForgotPassword(c fiber.Ctx) error {
	var req struct {
		Email string `json:"email" form:"email"`
	}

	if err := c.Bind().Body(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody),
		})
	}

	// Validate email
	if req.Email == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, responseErrors.ErrEmailRequired),
		})
	}

	// Request password reset - always return success to prevent email enumeration
	err := h.authService.RequestPasswordReset(req.Email)
	if err != nil {
		log.Printf("Password reset request error: %v", err)
	}

	return c.JSON(fiber.Map{
		"message": i18n.T_(c, "auth.auth_if_account_exists"),
	})
}

// ResetPassword handles password reset completion
func (h *AuthHandler) ResetPassword(c fiber.Ctx) error {
	var req model.ResetPasswordRequest

	if err := c.Bind().Body(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody),
		})
	}

	// Validate required fields
	if req.Token == "" || req.Password == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, responseErrors.ErrTokenPasswordRequired),
		})
	}

	// Reset password (also revokes all refresh tokens for the user)
	err := h.authService.ResetPassword(req.Token, req.Password)
	if err != nil {
		return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, err),
		})
	}

	return c.JSON(fiber.Map{
		"message": i18n.T_(c, "auth.auth_password_reset_successfully"),
	})
}

// Logout handles user logout — revokes the refresh token from DB and clears all cookies
func (h *AuthHandler) Logout(c fiber.Ctx) error {
	// Revoke the refresh token from the database
	rawRefreshToken := c.Cookies("refresh_token")
	if rawRefreshToken != "" {
		h.authService.RevokeRefreshToken(rawRefreshToken)
	}

	// Clear all auth cookies
	h.clearAuthCookies(c)

	return c.JSON(fiber.Map{
		"message": i18n.T_(c, "auth.auth_logged_out_successfully"),
	})
}

// RefreshToken handles token refresh — validates opaque refresh token, rotates it, issues new access token
func (h *AuthHandler) RefreshToken(c fiber.Ctx) error {
	// Get refresh token from HTTPOnly cookie (preferred) or request body (fallback for API clients)
	rawRefreshToken := c.Cookies("refresh_token")
	if rawRefreshToken == "" {
		var body struct {
			RefreshToken string `json:"refresh_token"`
		}
		if err := c.Bind().Body(&body); err == nil {
			rawRefreshToken = body.RefreshToken
		}
	}

	if rawRefreshToken == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, responseErrors.ErrRefreshTokenRequired),
		})
	}

	response, newRawRefreshToken, err := h.authService.RefreshToken(rawRefreshToken)
	if err != nil {
		// If refresh token is invalid/expired, clear cookies
		h.clearAuthCookies(c)
		return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, err),
		})
	}

	// Set new cookies (rotated refresh token + new access token)
	h.setAuthCookies(c, response.AccessToken, newRawRefreshToken)

	return c.JSON(response)
}

// Me returns the current user info
func (h *AuthHandler) Me(c fiber.Ctx) error {
	user := c.Locals("user")
	if user == nil {
		return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, responseErrors.ErrNotAuthenticated),
		})
	}

	return c.JSON(fiber.Map{
		"user": user,
	})
}

// Register handles user registration
func (h *AuthHandler) Register(c fiber.Ctx) error {
	var req model.RegisterRequest

	if err := c.Bind().Body(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody),
		})
	}

	// Validate required fields
	if req.FirstName == "" || req.LastName == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, responseErrors.ErrFirstLastNameRequired),
		})
	}

	// Validate required fields
	if req.Email == "" || req.Password == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, responseErrors.ErrEmailPasswordRequired),
		})
	}

	// Attempt registration
	err := h.authService.Register(&req)
	if err != nil {
		log.Printf("Register error: %v", err)
		return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, err),
		})
	}

	return c.Status(fiber.StatusCreated).JSON(fiber.Map{
		"message": i18n.T_(c, "auth.auth_registration_successful"),
	})
}

// CompleteRegistration handles completion of registration with password
func (h *AuthHandler) CompleteRegistration(c fiber.Ctx) error {
	var req model.CompleteRegistrationRequest

	if err := c.Bind().Body(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody),
		})
	}

	// Validate required fields
	if req.Token == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, responseErrors.ErrTokenRequired),
		})
	}

	// Attempt to complete registration
	response, rawRefreshToken, err := h.authService.CompleteRegistration(&req)
	if err != nil {
		return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, err),
		})
	}

	// Set cookies for web-based authentication
	h.setAuthCookies(c, response.AccessToken, rawRefreshToken)

	return c.Status(fiber.StatusCreated).JSON(response)
}

// Updates JWT Tokens. Requires authentication and updates access token only
func (h *AuthHandler) UpdateJWTToken(c fiber.Ctx) error {
	userLocals, ok := c.Locals(constdata.USER_LOCALES_NAME).(*model.UserSession)
	if !ok {
		return c.Status(fiber.StatusUnauthorized).
			JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrNotAuthenticated)))
	}

	user := model.Customer{
		ID:        userLocals.UserID,
		Email:     userLocals.Email,
		Role:      userLocals.Role,
		LangID:    userLocals.LangID,
		CountryID: userLocals.CountryID,
		IsActive:  userLocals.IsActive,
	}

	// Parse language and country_id from query params
	langIDStr := c.Query("lang_id")

	if langIDStr != "" {
		parsedID, err := strconv.ParseUint(langIDStr, 10, 32)
		if err != nil {
			return c.Status(fiber.StatusBadRequest).
				JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadLangID)))
		}
		user.LangID = uint(parsedID)
	}

	countryIDStr := c.Query("country_id")

	if countryIDStr != "" {
		parsedID, err := strconv.ParseUint(countryIDStr, 10, 32)
		if err != nil {
			return c.Status(fiber.StatusBadRequest).
				JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadCountryID)))
		}
		user.CountryID = uint(parsedID)
	}

	newAccessToken, err := h.authService.UpdateJWTToken(&user)

	if err != nil {
		return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, err),
		})
	}

	// does not reset refresh token
	h.setAuthCookies(c, newAccessToken, "")

	return c.JSON(response.Make(&fiber.Map{"token": newAccessToken}, 0, i18n.T_(c, response.Message_OK)))
}

// GoogleLogin redirects the user to Google's OAuth2 consent page
func (h *AuthHandler) GoogleLogin(c fiber.Ctx) error {
	// Generate a random state token and store it in a short-lived cookie
	state, err := h.authService.GenerateOAuthState()
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": i18n.T_(c, "error.err_internal_server_error"),
		})
	}

	c.Cookie(&fiber.Cookie{
		Name:     "oauth_state",
		Value:    state,
		Expires:  time.Now().Add(10 * time.Minute),
		HTTPOnly: true,
		Secure:   h.config.App.Environment == "production",
		SameSite: "Lax",
	})

	url := h.authService.GetGoogleAuthURL(state)
	return c.Redirect().To(url)
}

// GoogleCallback handles the OAuth2 callback from Google
func (h *AuthHandler) GoogleCallback(c fiber.Ctx) error {
	// Validate state to prevent CSRF
	cookieState := c.Cookies("oauth_state")
	queryState := c.Query("state")
	if cookieState == "" || cookieState != queryState {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": i18n.T_(c, "error.err_invalid_token"),
		})
	}

	// Clear the state cookie
	c.Cookie(&fiber.Cookie{
		Name:     "oauth_state",
		Value:    "",
		Expires:  time.Now().Add(-time.Hour),
		HTTPOnly: true,
		Secure:   h.config.App.Environment == "production",
		SameSite: "Lax",
	})

	code := c.Query("code")
	if code == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": i18n.T_(c, "error.err_invalid_body"),
		})
	}

	response, rawRefreshToken, err := h.authService.HandleGoogleCallback(code)
	if err != nil {
		log.Printf("Google OAuth callback error: %v", err)
		return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, err),
		})
	}

	// Set cookies for web-based authentication (including is_authenticated flag)
	h.setAuthCookies(c, response.AccessToken, rawRefreshToken)

	// Redirect to the locale-prefixed charts page after successful Google login.
	// The user's preferred language is stored in the auth response; fall back to "en".
	lang_iso_code, err := h.authService.GetLangISOCode(response.User.LangID)
	if err != nil {
		return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
			"error": responseErrors.GetErrorCode(c, err),
		})
	}

	return c.Redirect().To(h.config.App.BaseURL + "/" + lang_iso_code)
}
