package http_router

import (
	"context"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"log/slog"
	"net/http"
	"net/url"

	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/auth"
	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/auth/keycloak"
	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/broker"
	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
	dbtypes "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/types"
	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/feed"
	notification "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/notifications/git-molva.ru/Molva/molva-notification-service"
	rmodel "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/request_model"
	authinfra "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/services/auth_infrastructure"
	"google.golang.org/protobuf/proto"
)

// ------------------------------
//           LOGIN USER
// ------------------------------

// @Summary Вход пользователя
// @Description Аутентификация пользователя по email и паролю
// @Tags auth
// @Accept json
// @Produce json
// @Param request body rmodel.LoginUserRequest true "Данные для входа"
// @Success 200 {object} rmodel.LoginUserResponse "Успешная аутентификация"
// @Failure 400 {object} map[string]string "Неверные данные запроса"
// @Failure 401 {object} map[string]string "Неверные учетные данные"
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
// @Router /api/v1/login [post]
func (h *handler) loginHandler(w http.ResponseWriter, r *http.Request) {
	const handlerName = "loginHandler"

	var request rmodel.LoginUserRequest

	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
		h.logger.Error("Invalid request body",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	defer func(body io.ReadCloser) {
		if err := body.Close(); err != nil {
			http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
			h.logger.Error("error closing body",
				slog.String("error", err.Error()),
				slog.String("handler", handlerName))
		}
	}(r.Body)

	tokens, err := h.authManager.LoginUser(r.Context(), auth.LoginUserRequest{
		Email:    request.Email,
		Password: request.Password,
	})
	if err != nil {
		h.handleKeycloakError(w, err)
		h.logger.Error("error while login user",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	userInfo, err := h.authManager.GetUserInfo(r.Context(), tokens.UserId)
	if err != nil {
		h.handleKeycloakError(w, err)
		h.logger.Error("error while getting user info",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	emailVerificationStatus, err := h.authManager.GetEmailVerificationStatus(r.Context(), tokens.UserId)
	if err != nil {
		h.handleKeycloakError(w, err)
		h.logger.Error("error while getting email verification status",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	w.Header().Set("Content-Type", "application/json")

	if err = json.NewEncoder(w).Encode(rmodel.LoginUserResponse{
		Uid:          tokens.UserId,
		AccessToken:  tokens.AccessToken,
		RefreshToken: tokens.RefreshToken,
		UserType:     userInfo.UserType,
		Permissions: rmodel.Permissions{
			Balance:     userInfo.Permissions[keycloak.PermissionBalance],
			Company:     userInfo.Permissions[keycloak.PermissionCompany],
			Employees:   userInfo.Permissions[keycloak.PermissionEmployees],
			Profile:     userInfo.Permissions[keycloak.PermissionProfile],
			Submissions: userInfo.Permissions[keycloak.PermissionSubmissions],
			Vacancies:   userInfo.Permissions[keycloak.PermissionVacancies],
		},
		EmailVerified: emailVerificationStatus.EmailVerified,
	}); err != nil {
		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
		h.logger.Error("error while encoding response: ",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))
	}
}

// ------------------------------
//         REGISTER USER
// ------------------------------

// @Summary Регистрация пользователя
// @Description Создание нового пользователя в системе
// @Tags auth
// @Accept json
// @Produce json
// @Param request body rmodel.UserCredentials true "Данные для регистрации"
// @Success 201 {object} rmodel.RegisterResponse "Пользователь успешно создан"
// @Failure 400 {object} map[string]string "Неверные данные запроса"
// @Failure 409 {object} map[string]string "Пользователь уже существует"
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
// @Router /api/v1/register [post]
func (h *handler) registerHandler(w http.ResponseWriter, r *http.Request) {
	const handlerName = "registerHandler"

	var creds rmodel.UserCredentials

	if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
		http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
		h.logger.Error("error decoding request body",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	defer r.Body.Close()

	if h.env == "production" {
		isEmailValid, err := h.validateEmail(creds.Email)
		if err != nil {
			h.logger.Warn("EmailVerificationService API service error, proceeding with email validation via message",
				slog.String("error", err.Error()),
				slog.String("email", creds.Email),
				slog.String("handler", handlerName))
		} else if !isEmailValid {
			h.logger.Warn("email validation failed - invalid email address",
				slog.String("email", creds.Email),
				slog.String("handler", handlerName))
			http.Error(w, "Invalid email address", http.StatusBadRequest)

			return
		}
	}

	uid, err := h.authManager.RegisterUser(r.Context(), auth.RegisterUserRequest{
		User: auth.User{
			Email:       creds.Email,
			Password:    creds.Password,
			FirstName:   creds.FirstName,
			SecondName:  creds.LastName,
			Patronymic:  creds.MiddleName,
			Number:      creds.PhoneNumber,
			Permissions: creds.Permissions,
		},
		UserType: creds.UserType,
	})
	if err != nil {
		h.logger.Error("error while saving user to keycloak",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))
		h.handleKeycloakError(w, err)

		return
	}

	if err := h.saveUser(r.Context(), uid.UserId, creds); err != nil {
		h.handleDBError(w, err)
		h.logger.Error("error saving user to DB",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	tokenResp, err := h.authManager.GetUserEmailVerificationToken(r.Context(), uid.UserId)
	if err != nil {
		h.logger.Error("error getting user token",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))
		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)

		return
	}

	userName := formatName(creds.LastName, creds.FirstName, creds.MiddleName)

	if err := h.sendConfirmationEmail(r, creds.Email, userName, uid.UserId, tokenResp.AccessToken); err != nil {
		h.logger.Error("error sending confirmation email",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))
		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)

		return
	}

	h.createWelcomeEvent(r.Context(), uid.UserId, creds, handlerName)

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)

	if err := json.NewEncoder(w).Encode(&rmodel.RegisterResponse{UUID: uid.UserId}); err != nil {
		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
		h.logger.Error("error while encoding response", slog.String("error", err.Error()),
			slog.String("handler", handlerName))
	}
}

func (h *handler) createWelcomeEvent(ctx context.Context, userID string, creds rmodel.UserCredentials, handlerName string) {
	ownerType := feed.RoleAgent
	if creds.UserType == constants.DistributorClientType {
		ownerType = feed.RoleDistributor
	}

	event := feed.Event{
		OwnerId:    userID,
		OwnerType:  ownerType,
		EventType:  feed.EventWelcome,
		Message:    "Добро пожаловать в Molva!",
		Visibility: feed.VisibilityPrivate,
		Payload:    feed.EventPayload{},
	}

	if err := h.feed.CreateEvent(ctx, &event); err != nil {
		h.logger.Error("failed to create feed event",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName),
			slog.String("uid", userID))
	}
}

func (h *handler) sendConfirmationEmail(r *http.Request, email, userName, userID, accessToken string) error {
	scheme := "https"
	if r.TLS == nil {
		scheme = "http"
	}

	confirmURL := fmt.Sprintf("%s://%s/api/v1/confirm_email?uid=%s&token=%s",
		scheme,
		r.Host,
		url.QueryEscape(userID),
		url.QueryEscape(accessToken),
	)

	emailBody := fmt.Sprintf(constants.EmailVerificationTemplate,
		userName,
		confirmURL,
		confirmURL,
	)

	msg, err := proto.Marshal(&notification.SendEmailRequest{
		To:          []string{email},
		Subject:     constants.EmailVerificationMessageSubject,
		ContentType: constants.HTMLNotificationContentType,
		Body:        []byte(emailBody),
	})
	if err != nil {
		return fmt.Errorf("marshaling email message: %w", err)
	}

	if err := broker.SendNotification(
		broker.NotificationQueue,
		constants.EmailNotificationMessageType,
		msg,
		h.logger,
	); err != nil {
		return fmt.Errorf("sending notification: %w", err)
	}

	return nil
}

// NOTE: lastName and firstName are required to be non-empty
func formatName(lastName, firstName string, middleName *string) string {
	name := fmt.Sprintf("%s %s", lastName, firstName)

	if middleName != nil {
		name = fmt.Sprintf("%s %s", name, *middleName)
	}

	return name
}

func (h *handler) saveUser(ctx context.Context, uid string, creds rmodel.UserCredentials) error {
	userName := formatName(creds.LastName, creds.FirstName, creds.MiddleName)

	if _, err := h.dbClient.CreateUser(ctx, &dbtypes.UserSaveRequest{
		Id:       uid,
		FullName: userName,
		Phone:    creds.PhoneNumber,
		Email:    creds.Email,
		Type:     dbtypes.UserType(creds.UserType),
	}); err != nil {
		return err
	}

	return nil
}

// ------------------------------
//         LOGOUT USER
// ------------------------------

// @Summary Выход пользователя
// @Description Завершение сессии пользователя
// @Tags auth
// @Accept json
// @Produce json
// @Param request body rmodel.LogoutUserRequest true "Токен для выхода"
// @Success 200 "Успешный выход"
// @Failure 400 {object} map[string]string "Неверные данные запроса"
// @Failure 401 {object} map[string]string "Неверный токен"
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
// @Router /api/v1/logout [post]
func (h *handler) logoutHandler(w http.ResponseWriter, r *http.Request) {
	const handlerName = "logoutHandler"

	var request rmodel.LogoutUserRequest

	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusBadRequest)
		h.logger.Error("error while decoding request body: ",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	if err := h.authManager.LogoutUser(r.Context(), auth.LogoutUserRequest{
		RefreshToken: request.RefreshToken,
	}); err != nil {
		h.handleKeycloakError(w, err)
		return
	}
}

// ------------------------------
//       REFRESH USER TOKEN
// ------------------------------

// @Summary Обновление токена доступа
// @Description Получение нового access token по refresh token
// @Tags auth
// @Accept json
// @Produce json
// @Param request body rmodel.RefreshTokenRequest true "Refresh token"
// @Success 200 {object} rmodel.RefreshTokenResponse "Новые токены"
// @Failure 400 {object} map[string]string "Неверные данные запроса"
// @Failure 401 {object} map[string]string "Неверный refresh token"
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
// @Router /api/v1/refresh-token [post]
func (h *handler) refreshTokenHandler(w http.ResponseWriter, r *http.Request) {
	const handlerName = "refreshTokenHandler"

	var request rmodel.RefreshTokenRequest

	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusBadRequest)
		h.logger.Error("error while decoding request body: ",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	resp, err := h.authManager.GetNewAccessToken(r.Context(), auth.GetNewAccessTokenRequest{
		RefreshToken: request.RefreshToken,
	})
	if err != nil {
		h.handleKeycloakError(w, err)
		return
	}

	if err := json.NewEncoder(w).Encode(rmodel.RefreshTokenResponse{
		AccessToken:  resp.AccessToken,
		RefreshToken: resp.RefreshToken,
	}); err != nil {
		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
		h.logger.Error("error while encoding response: ",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}
}

// @Summary Страница подтверждения email
// @Description HTML страница для подтверждения email адреса пользователя
// @Tags auth
// @Accept html
// @Produce html
// @Param uid query string true "ID пользователя"
// @Param token query string true "Токен подтверждения"
// @Success 200 {string} string "HTML страница подтверждения"
// @Failure 400 {object} map[string]string "Неверные параметры запроса"
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
// @Router /api/v1/confirm_email [get]
func (h *handler) confirmEmailPageHandler(w http.ResponseWriter, r *http.Request) {
	const handlerName = "confirmEmailPageHandler"

	if r.Method != http.MethodGet {
		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
		return
	}

	uid := r.URL.Query().Get("uid")
	token := r.URL.Query().Get("token")

	if uid == "" || token == "" {
		http.Error(w, "Bad Request: отсутствует uid или token", http.StatusBadRequest)
		return
	}

	scheme := "http"

	if protocol := r.Header.Get("X-Forwarded-Proto"); protocol != "" {
		scheme = protocol
	} else if h.env == "production" || h.env == "development" {
		scheme = "https"
	}

	// Формируем URL для PATCH-запроса
	verificationLink := fmt.Sprintf("%s://%s/api/v1/verify_email?uid=%s&token=%s",
		scheme,
		r.Host,
		url.QueryEscape(uid),
		url.QueryEscape(token),
	)

	tmpl, err := template.New("confirm").Parse(constants.EmailConfirmationPage)
	if err != nil {
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "text/html; charset=utf-8")

	if err := tmpl.Execute(w, struct {
		VerificationURL string
	}{
		VerificationURL: verificationLink,
	}); err != nil {
		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
		h.logger.Error("error while executing confirm email page template",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))
	}
}

// ------------------------------
// 	  	  RESET PASSWORD
// ------------------------------

// @Summary Запрос восстановления пароля
// @Description Отправка OTP кода на email для восстановления пароля
// @Tags auth
// @Accept json
// @Produce json
// @Param request body rmodel.ForgotPasswordRequest true "Данные для восстановления пароля"
// @Success 200 {object} map[string]string "OTP код отправлен"
// @Failure 400 {object} map[string]string "Неверные данные запроса"
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
// @Router /api/v1/forgot_password [post]
func (h *handler) forgotPasswordHandler(w http.ResponseWriter, r *http.Request) {
	const handlerName = "forgotPasswordHandler"

	var request rmodel.ForgotPasswordRequest

	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
		h.logger.Error("error while decoding request body: ",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	createOTPResp, err := h.authInfraService.CreatePasswordResetOTP(r.Context(), &authinfra.PasswordResetOTPCreateRequest{
		Email: request.Email,
	})
	if err != nil {
		h.handleAuthInfraError(w, err, handlerName)
		return
	}

	message, err := proto.Marshal(&notification.SendEmailRequest{
		To:          []string{request.Email},
		Subject:     constants.ForgotPasswordNotificationMessageSubject,
		ContentType: constants.TextNotificationContentType,
		Body: fmt.Appendf(nil,
			constants.ForgotPasswordNotificationText,
			createOTPResp.OTP,
		),
	})
	if err != nil {
		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
		h.logger.Error("error while marshaling forgot password notification message",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	if err := broker.SendNotification(
		broker.NotificationQueue,
		constants.EmailNotificationMessageType,
		message,
		h.logger,
	); err != nil {
		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
		h.logger.Error("error while sending forgot password notification",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	w.WriteHeader(http.StatusCreated)
}

// @Summary Валидация OTP кода
// @Description Проверка OTP кода для восстановления пароля
// @Tags auth
// @Accept json
// @Produce json
// @Param otp query string true "OTP код"
// @Param email query string true "Email пользователя"
// @Success 200 {object} map[string]string "OTP код валиден"
// @Failure 400 {object} map[string]string "Неверные параметры запроса"
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
// @Router /api/v1/validate_otp [get]
func (h *handler) validateOTPHandler(w http.ResponseWriter, r *http.Request) {
	const handlerName = "validateOTPHandler"

	query := r.URL.Query()

	email := query.Get("email")
	if email == "" {
		http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
		h.logger.Debug("email is required, but missing",
			slog.String("handler", handlerName))

		return
	}

	otp := query.Get("otp")
	if otp == "" {
		http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
		h.logger.Debug("OTP is required, but missing",
			slog.String("handler", handlerName))

		return
	}

	checkOTPResp, err := h.authInfraService.ValidatePasswordResetOTP(r.Context(), &authinfra.ValidatePasswordResetOTPRequest{
		Email: email,
		OTP:   otp,
	})
	if err != nil {
		h.handleAuthInfraError(w, err, handlerName)
		return
	}

	w.Header().Set("Content-Type", "application/json")

	if err := json.NewEncoder(w).Encode(rmodel.ValidateOTPResponse{
		Token: checkOTPResp.Token,
	}); err != nil {
		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
		h.logger.Error("error encoding response: ",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))
	}
}

// @Summary Сброс пароля
// @Description Установка нового пароля после валидации OTP кода
// @Tags auth
// @Accept json
// @Produce json
// @Param request body rmodel.ResetPasswordRequest true "Данные для сброса пароля"
// @Success 200 {object} map[string]string "Пароль успешно изменен"
// @Failure 400 {object} map[string]string "Неверные данные запроса"
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
// @Router /api/v1/reset_password [put]
func (h *handler) resetPasswordHandler(w http.ResponseWriter, r *http.Request) {
	const handlerName = "resetPasswordHandler"

	var request rmodel.ResetPasswordRequest

	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
		h.logger.Error("error decoding request body: ",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	if _, err := h.authInfraService.ValidatePasswordResetToken(r.Context(), &authinfra.ValidatePasswordResetTokenRequest{
		Email: request.Email,
		Token: request.Token,
	}); err != nil {
		h.handleAuthInfraError(w, err, handlerName)
		return
	}

	if err := h.authManager.ResetPassword(r.Context(), auth.ResetPasswordRequest{
		Email:       request.Email,
		NewPassword: request.Password,
	}); err != nil {
		h.handleKeycloakError(w, err)
		h.logger.Error("error resetting password",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	w.WriteHeader(http.StatusNoContent)
}
