package handlers

import (
	"errors"
	"fmt"
	"strconv"
	"time"

	"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
	"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
	"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"

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

type CheckPhoneEmailExistReq struct {
	Email       string `json:"email" example:"john.doe@example.com"`
	PhoneNumber string `json:"phone_number" example:"1234567890"`
}
type CheckPhoneEmailExistRes struct {
	EmailExist       bool `json:"email_exist"`
	PhoneNumberExist bool `json:"phone_number_exist"`
}

// CheckPhoneEmailExist godoc
// @Summary Check if phone number or email exist
// @Description Check if phone number or email exist
// @Tags user
// @Accept json
// @Produce json
// @Param checkPhoneEmailExist body CheckPhoneEmailExistReq true "Check phone number or email exist"
// @Success 200 {object} CheckPhoneEmailExistRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/checkPhoneEmailExist [post]
func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error {

	var req CheckPhoneEmailExistReq
	if err := c.BodyParser(&req); err != nil {
		h.logger.Error("Failed to parse CheckPhoneEmailExist request", "error", err)
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	if valErrs, ok := h.validator.Validate(c, req); !ok {
		return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
	}

	emailExist, phoneExist, err := h.userSvc.CheckPhoneEmailExist(c.Context(), req.PhoneNumber, req.Email)
	if err != nil {
		h.logger.Error("Failed to check phone/email existence", "error", err)
		return fiber.NewError(fiber.StatusInternalServerError, "Failed to check phone/email existence")
	}

	res := CheckPhoneEmailExistRes{
		EmailExist:       emailExist,
		PhoneNumberExist: phoneExist,
	}
	return response.WriteJSON(c, fiber.StatusOK, "Check successful", res, nil)
}

type RegisterCodeReq struct {
	Email       string `json:"email" example:"john.doe@example.com"`
	PhoneNumber string `json:"phone_number" example:"1234567890"`
}

// SendRegisterCode godoc
// @Summary Send register code
// @Description Send register code
// @Tags user
// @Accept json
// @Produce json
// @Param registerCode body RegisterCodeReq true "Send register code"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/sendRegisterCode [post]
func (h *Handler) SendRegisterCode(c *fiber.Ctx) error {

	var req RegisterCodeReq
	if err := c.BodyParser(&req); err != nil {
		h.logger.Error("Failed to parse SendRegisterCode request", "error", err)
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	if valErrs, ok := h.validator.Validate(c, req); !ok {
		return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
	}

	var sentTo string
	var medium domain.OtpMedium
	if req.Email != "" {
		sentTo = req.Email
		medium = domain.OtpMediumEmail
	} else if req.PhoneNumber != "" {
		sentTo = req.PhoneNumber
		medium = domain.OtpMediumSms
	} else {
		return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided")
	}

	if err := h.userSvc.SendRegisterCode(c.Context(), medium, sentTo); err != nil {
		h.logger.Error("Failed to send register code", "error", err)
		return fiber.NewError(fiber.StatusInternalServerError, "Failed to send register code")
	}

	return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil)
}

type RegisterUserReq struct {
	FirstName   string `json:"first_name" example:"John"`
	LastName    string `json:"last_name" example:"Doe"`
	Email       string `json:"email" example:"john.doe@example.com"`
	PhoneNumber string `json:"phone_number" example:"1234567890"`
	Password    string `json:"password" example:"password123"`
	Otp         string `json:"otp" example:"123456"`
	ReferalCode string `json:"referal_code" example:"ABC123"`
}

// RegisterUser godoc
// @Summary Register user
// @Description Register user
// @Tags user
// @Accept json
// @Produce json
// @Param registerUser body RegisterUserReq true "Register user"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/register [post]
func (h *Handler) RegisterUser(c *fiber.Ctx) error {

	var req RegisterUserReq
	if err := c.BodyParser(&req); err != nil {
		h.logger.Error("Failed to parse RegisterUser request", "error", err)
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	if valErrs, ok := h.validator.Validate(c, req); !ok {
		return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
	}
	user := domain.RegisterUserReq{
		FirstName:    req.FirstName,
		LastName:     req.LastName,
		Email:        req.Email,
		PhoneNumber:  req.PhoneNumber,
		Password:     req.Password,
		Otp:          req.Otp,
		ReferralCode: req.ReferalCode,
		OtpMedium:    domain.OtpMediumEmail,
	}
	medium, err := getMedium(req.Email, req.PhoneNumber)
	if err != nil {
		h.logger.Error("RegisterUser failed", "error", err)
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
	}

	user.OtpMedium = medium

	newUser, err := h.userSvc.RegisterUser(c.Context(), user)
	if err != nil {
		if errors.Is(err, domain.ErrOtpAlreadyUsed) {
			return fiber.NewError(fiber.StatusBadRequest, "Otp already used")
		}
		if errors.Is(err, domain.ErrOtpExpired) {
			return fiber.NewError(fiber.StatusBadRequest, "Otp expired")
		}
		if errors.Is(err, domain.ErrInvalidOtp) {
			return fiber.NewError(fiber.StatusBadRequest, "Invalid otp")
		}
		if errors.Is(err, domain.ErrOtpNotFound) {
			return fiber.NewError(fiber.StatusBadRequest, "User already exist")
		}
		h.logger.Error("RegisterUser failed", "error", err)
		return fiber.NewError(fiber.StatusInternalServerError, "Unknown Error")
	}

	newWallet, err := h.walletSvc.CreateWallet(c.Context(), domain.CreateWallet{
		UserID:     newUser.ID,
		IsWithdraw: true,
		IsBettable: true,
	})
	if err != nil {
		h.logger.Error("Failed to create wallet for user", "userID", newUser.ID, "error", err)
		return fiber.NewError(fiber.StatusInternalServerError, "Failed to create user wallet")
	}

	if req.ReferalCode != "" {
		err = h.referralSvc.ProcessReferral(c.Context(), req.PhoneNumber, req.ReferalCode)
		if err != nil {
			h.logger.Warn("Failed to process referral during registration", "phone", req.PhoneNumber, "code", req.ReferalCode, "error", err)
		}
	}

	// TODO: Remove later
	err = h.walletSvc.AddToWallet(c.Context(), newWallet.ID, domain.ToCurrency(100.0))

	if err != nil {
		h.logger.Error("Failed to update wallet for user", "userID", newUser.ID, "error", err)
		return fiber.NewError(fiber.StatusInternalServerError, "Failed to update user wallet")
	}

	return response.WriteJSON(c, fiber.StatusOK, "Registration successful", nil, nil)
}

type ResetCodeReq struct {
	Email       string `json:"email" example:"john.doe@example.com"`
	PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
}

// SendResetCode godoc
// @Summary Send reset code
// @Description Send reset code
// @Tags user
// @Accept json
// @Produce json
// @Param resetCode body ResetCodeReq true "Send reset code"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/sendResetCode [post]
func (h *Handler) SendResetCode(c *fiber.Ctx) error {

	var req ResetCodeReq
	if err := c.BodyParser(&req); err != nil {
		h.logger.Error("Failed to parse SendResetCode request", "error", err)
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	if valErrs, ok := h.validator.Validate(c, req); !ok {
		return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
	}

	var sentTo string
	var medium domain.OtpMedium
	if req.Email != "" {
		sentTo = req.Email
		medium = domain.OtpMediumEmail
	} else if req.PhoneNumber != "" {
		sentTo = req.PhoneNumber
		medium = domain.OtpMediumSms
	} else {
		return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided")
	}

	if err := h.userSvc.SendResetCode(c.Context(), medium, sentTo); err != nil {
		h.logger.Error("Failed to send reset code", "error", err)
		fmt.Println(err)
		return fiber.NewError(fiber.StatusInternalServerError, "Failed to send reset code")
	}

	return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil)
}

type ResetPasswordReq struct {
	Email       string `json:"email,omitempty" validate:"required_without=PhoneNumber,omitempty,email" example:"john.doe@example.com"`
	PhoneNumber string `json:"phone_number,omitempty" validate:"required_without=Email,omitempty" example:"1234567890"`
	Password    string `json:"password" validate:"required,min=8" example:"newpassword123"`
	Otp         string `json:"otp" validate:"required" example:"123456"`
}

// ResetPassword godoc
// @Summary Reset password
// @Description Reset password
// @Tags user
// @Accept json
// @Produce json
// @Param resetPassword body ResetPasswordReq true "Reset password"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/resetPassword [post]
func (h *Handler) ResetPassword(c *fiber.Ctx) error {

	var req ResetPasswordReq
	if err := c.BodyParser(&req); err != nil {
		h.logger.Error("Failed to parse ResetPassword request", "error", err)
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	if valErrs, ok := h.validator.Validate(c, req); !ok {
		return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
	}

	medium, err := getMedium(req.Email, req.PhoneNumber)
	if err != nil {
		h.logger.Error("Failed to determine medium for ResetPassword", "error", err)
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
	}

	resetReq := domain.ResetPasswordReq{
		Email:       req.Email,
		PhoneNumber: req.PhoneNumber,
		Password:    req.Password,
		Otp:         req.Otp,
		OtpMedium:   medium,
	}

	if err := h.userSvc.ResetPassword(c.Context(), resetReq); err != nil {
		h.logger.Error("Failed to reset password", "error", err)
		return fiber.NewError(fiber.StatusInternalServerError, "Failed to reset password")
	}

	return response.WriteJSON(c, fiber.StatusOK, "Password reset successful", nil, nil)
}

type UserProfileRes struct {
	ID            int64       `json:"id"`
	FirstName     string      `json:"first_name"`
	LastName      string      `json:"last_name"`
	Email         string      `json:"email"`
	PhoneNumber   string      `json:"phone_number"`
	Role          domain.Role `json:"role"`
	EmailVerified bool        `json:"email_verified"`
	PhoneVerified bool        `json:"phone_verified"`
	CreatedAt     time.Time   `json:"created_at"`
	UpdatedAt     time.Time   `json:"updated_at"`
	LastLogin     time.Time   `json:"last_login"`
	SuspendedAt   time.Time   `json:"suspended_at"`
	Suspended     bool        `json:"suspended"`
}

// UserProfile godoc
// @Summary Get user profile
// @Description Get user profile
// @Tags user
// @Accept json
// @Produce json
// @Success 200 {object} UserProfileRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Security Bearer
// @Router /user/profile [get]
func (h *Handler) UserProfile(c *fiber.Ctx) error {

	userID, ok := c.Locals("user_id").(int64)
	if !ok || userID == 0 {
		h.logger.Error("Invalid user ID in context")
		return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
	}

	user, err := h.userSvc.GetUserByID(c.Context(), userID)
	if err != nil {
		h.logger.Error("Failed to get user profile", "userID", userID, "error", err)
		return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user profile")
	}

	lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
	if err != nil {
		if err != authentication.ErrRefreshTokenNotFound {
			h.logger.Error("Failed to get user last login", "userID", userID, "error", err)
			return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
		}

		lastLogin = &user.CreatedAt
	}
	res := UserProfileRes{
		ID:            user.ID,
		FirstName:     user.FirstName,
		LastName:      user.LastName,
		Email:         user.Email,
		PhoneNumber:   user.PhoneNumber,
		Role:          user.Role,
		EmailVerified: user.EmailVerified,
		PhoneVerified: user.PhoneVerified,
		CreatedAt:     user.CreatedAt,
		UpdatedAt:     user.UpdatedAt,
		SuspendedAt:   user.SuspendedAt,
		Suspended:     user.Suspended,
		LastLogin:     *lastLogin,
	}
	return response.WriteJSON(c, fiber.StatusOK, "User profile retrieved successfully", res, nil)
}

// Helper function (unchanged)
func getMedium(email, phoneNumber string) (domain.OtpMedium, error) {
	if email != "" {
		return domain.OtpMediumEmail, nil
	}
	if phoneNumber != "" {
		return domain.OtpMediumSms, nil
	}
	return "", errors.New("both email and phone number are empty")
}

type SearchUserByNameOrPhoneReq struct {
	SearchString string       `json:"query"`
	Role         *domain.Role `json:"role,omitempty"`
}

// SearchUserByNameOrPhone godoc
// @Summary Search for user using name or phone
// @Description Search for user using name or phone
// @Tags user
// @Accept json
// @Produce json
// @Param searchUserByNameOrPhone body SearchUserByNameOrPhoneReq true "Search for using his name or phone"
// @Success 200 {object} UserProfileRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/search [post]
func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error {
	// TODO: Add filtering by role based on which user is calling this
	var req SearchUserByNameOrPhoneReq
	if err := c.BodyParser(&req); err != nil {
		h.logger.Error("SearchUserByNameOrPhone failed", "error", err)
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid request",
		})
	}
	valErrs, ok := h.validator.Validate(c, req)
	if !ok {
		response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
		return nil
	}
	companyID := c.Locals("company_id").(domain.ValidInt64)
	users, err := h.userSvc.SearchUserByNameOrPhone(c.Context(), req.SearchString, req.Role, companyID)
	if err != nil {
		h.logger.Error("SearchUserByNameOrPhone failed", "error", err)
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": "Internal server error",
		})
	}
	var res []UserProfileRes = make([]UserProfileRes, 0, len(users))
	for _, user := range users {
		lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
		if err != nil {
			if err != authentication.ErrRefreshTokenNotFound {
				h.logger.Error("Failed to get user last login", "userID", user.ID, "error", err)
				return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
			}

			lastLogin = &user.CreatedAt
		}
		res = append(res, UserProfileRes{
			ID:            user.ID,
			FirstName:     user.FirstName,
			LastName:      user.LastName,
			Email:         user.Email,
			PhoneNumber:   user.PhoneNumber,
			Role:          user.Role,
			EmailVerified: user.EmailVerified,
			PhoneVerified: user.PhoneVerified,
			CreatedAt:     user.CreatedAt,
			UpdatedAt:     user.UpdatedAt,
			SuspendedAt:   user.SuspendedAt,
			Suspended:     user.Suspended,
			LastLogin:     *lastLogin,
		})
	}
	return response.WriteJSON(c, fiber.StatusOK, "Search Successful", res, nil)

}

// GetUserByID godoc
// @Summary Get user by id
// @Description Get a single user by id
// @Tags user
// @Accept json
// @Produce json
// @Param id path int true "User ID"
// @Success 200 {object} UserProfileRes
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/single/{id} [get]
func (h *Handler) GetUserByID(c *fiber.Ctx) error {
	// branchId := int64(12) //c.Locals("branch_id").(int64)
	// filter := user.Filter{
	// 	Role: string(domain.RoleUser),
	// 	BranchId: user.ValidBranchId{
	// 		Value: branchId,
	// 		Valid: true,
	// 	},
	// 	Page:     c.QueryInt("page", 1),
	// 	PageSize: c.QueryInt("page_size", 10),
	// }
	// valErrs, ok := validator.Validate(c, filter)
	// if !ok {
	// 	return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
	// }

	userIDstr := c.Params("id")
	userID, err := strconv.ParseInt(userIDstr, 10, 64)
	if err != nil {
		h.logger.Error("failed to fetch user using UserID", "error", err)
		return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashier ID", nil, nil)
	}

	user, err := h.userSvc.GetUserByID(c.Context(), userID)
	if err != nil {
		h.logger.Error("Get User By ID failed", "error", err)
		return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil)
	}

	lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
	if err != nil {
		if err != authentication.ErrRefreshTokenNotFound {
			h.logger.Error("Failed to get user last login", "userID", user.ID, "error", err)
			return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login")
		}

		lastLogin = &user.CreatedAt
	}

	res := UserProfileRes{
		ID:            user.ID,
		FirstName:     user.FirstName,
		LastName:      user.LastName,
		Email:         user.Email,
		PhoneNumber:   user.PhoneNumber,
		Role:          user.Role,
		EmailVerified: user.EmailVerified,
		PhoneVerified: user.PhoneVerified,
		CreatedAt:     user.CreatedAt,
		UpdatedAt:     user.UpdatedAt,
		SuspendedAt:   user.SuspendedAt,
		Suspended:     user.Suspended,
		LastLogin:     *lastLogin,
	}

	return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil)

}

// DeleteUser godoc
// @Summary Delete user by ID
// @Description Delete a user by their ID
// @Tags user
// @Accept json
// @Produce json
// @Param id path int true "User ID"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/delete/{id} [delete]
func (h *Handler) DeleteUser(c *fiber.Ctx) error {
	userIDstr := c.Params("id")
	userID, err := strconv.ParseInt(userIDstr, 10, 64)
	if err != nil {
		h.logger.Error("DeleteUser failed", "error", err)
		return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid user ID", nil, nil)
	}

	err = h.userSvc.DeleteUser(c.Context(), userID)
	if err != nil {
		h.logger.Error("Failed to delete user", "userID", userID, "error", err)
		return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to delete user", nil, nil)
	}

	return response.WriteJSON(c, fiber.StatusOK, "User deleted successfully", nil, nil)
}

type UpdateUserSuspendReq struct {
	UserID    int64 `json:"user_id" validate:"required" example:"123"`
	Suspended bool  `json:"suspended" validate:"required" example:"true"`
}
type UpdateUserSuspendRes struct {
	UserID    int64 `json:"user_id"`
	Suspended bool  `json:"suspended"`
}

// UpdateUserSuspend godoc
// @Summary Suspend or unsuspend a user
// @Description Suspend or unsuspend a user
// @Tags user
// @Accept json
// @Produce json
// @Param updateUserSuspend body UpdateUserSuspendReq true "Suspend or unsuspend a user"
// @Success 200 {object} UpdateUserSuspendRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/suspend [post]
func (h *Handler) UpdateUserSuspend(c *fiber.Ctx) error {
	var req UpdateUserSuspendReq
	if err := c.BodyParser(&req); err != nil {
		h.logger.Error("Failed to parse UpdateUserSuspend request", "error", err)
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	if valErrs, ok := h.validator.Validate(c, req); !ok {
		return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
	}

	err := h.userSvc.UpdateUserSuspend(c.Context(), req.UserID, req.Suspended)
	if err != nil {
		h.logger.Error("Failed to update user suspend status", "error", err)
		return fiber.NewError(fiber.StatusInternalServerError, "Failed to update user suspend status")
	}

	res := UpdateUserSuspendRes{
		UserID:    req.UserID,
		Suspended: req.Suspended,
	}
	return response.WriteJSON(c, fiber.StatusOK, "User suspend status updated successfully", res, nil)
}

// GetBetByUserID godoc
// @Summary Gets user bets
// @Description Gets user bets
// @Tags user
// @Accept json
// @Produce json
// @Success 200 {array} domain.BetRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/bets [get]
func (h *Handler) GetBetByUserID(c *fiber.Ctx) error {
	userID, ok := c.Locals("user_id").(int64)
	if !ok || userID == 0 {
		h.logger.Error("Invalid user ID in context")
		return fiber.NewError(fiber.StatusInternalServerError, "Invalid user identification")
	}

	bets, err := h.betSvc.GetBetByUserID(c.Context(), userID)
	if err != nil {
		h.logger.Error("Failed to get bets", "error", err)
		return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bets")
	}

	res := make([]domain.BetRes, len(bets))
	for i, bet := range bets {
		res[i] = domain.ConvertBet(bet)
	}

	return response.WriteJSON(c, fiber.StatusOK, "User bets retrieved successfully", res, nil)
}
