package http_router

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"log/slog"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/cache"
	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/feed"
	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/object_storage"
	notification "github.com/AlexOreL-272/ProtoMolva/go/gen/notifications"
	"github.com/aws/smithy-go/ptr"
	"google.golang.org/protobuf/proto"

	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/auth/keycloak"
	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
	"github.com/google/uuid"

	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/auth"
	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/broker"
	dbtypes "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/types"
	formgenerator "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/form_generator"
	rmodel "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/request_model"
	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/types"
	"github.com/gorilla/mux"
)

func (h *handler) getPersonalLinkHandler(w http.ResponseWriter, r *http.Request) {
	handlerName := "getPersonalLinkHandler"
	vars := mux.Vars(r)
	agentId := vars["agent_id"]
	vacancyId := vars["vacancy_id"]

	// formatting string
	linkParams := fmt.Sprintf("%s|%s", agentId, vacancyId)

	// encryption string
	encryptedLink, err := h.urlShortener.Shorten(linkParams)
	if err != nil {
		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
		h.logger.Error("error while encrypting link: ",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	personalLink := fmt.Sprintf("%s/api/v1/anketa?link=%s", r.Host, encryptedLink)

	resp := types.PersonalLinkResponse{
		Link: personalLink,
	}

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

	if err = json.NewEncoder(w).Encode(resp); err != nil {
		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
		h.logger.Error("error while marshalling request: ",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}
}

//nolint:funlen // TODO: make it sudo super clean
func (h *handler) getAnketaHandler(w http.ResponseWriter, r *http.Request) {
	handlerName := "getAnketaHandler"
	encodedLink := r.URL.Query().Get("link")

	if encodedLink == "" {
		http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
		h.logger.Error("anketa link is empty: ",
			slog.String("handler", handlerName))

		return
	}

	link, err := h.urlShortener.Unshorten(encodedLink)
	if err != nil {
		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
		h.logger.Error("error while unshorting link: ",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	linkParams := strings.Split(link, "|")

	if len(linkParams) != 2 {
		http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
		h.logger.Error("Invalid link",
			slog.String("handler", handlerName))

		return
	}

	agentId, vacancyId := linkParams[0], linkParams[1]

	h.logger.Debug("serving client form: ",
		slog.String("agentID", agentId),
		slog.String("vacancyID", vacancyId),
		slog.String("handler", handlerName),
	)

	resp, err := h.dbClient.GetVacancyList(r.Context(), &dbtypes.VacancyListGetRequest{
		Filters: &dbtypes.VacancyListFilters{
			VacancyId: &vacancyId,
		},
	})
	if err != nil {
		h.handleDBError(w, err)
		h.logger.Error("error getting vacancy info",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	if len(resp.Vacancies) == 0 {
		// maybe still serve the form but without vacancy details?
		http.Error(w, constants.ErrNotFound.Error(), http.StatusNotFound)
		h.logger.Error("Vacancy info not found",
			slog.String("handler", handlerName))

		return
	}

	vacancyInfo := resp.Vacancies[0]

	h.logger.Debug("found vacancy info: ",
		slog.Any("vacancy", vacancyInfo),
		slog.String("handler", handlerName),
	)

	formVacancyInfo := map[string]string{
		"AgentId":   agentId,
		"VacancyId": vacancyId,

		"VacancyName":  vacancyInfo.Name,
		"Address":      vacancyInfo.Address,
		"WorkFormat":   vacancyInfo.WorkFormat,
		"SalaryTop":    fmt.Sprintf("%d", vacancyInfo.SalaryTop),
		"SalaryBottom": fmt.Sprintf("%d", vacancyInfo.SalaryBottom),
	}

	if vacancyInfo.Requirements != nil {
		formVacancyInfo["Requirements"] = *vacancyInfo.Requirements
	}

	if vacancyInfo.Responsibilities != nil {
		formVacancyInfo["Responsibilities"] = *vacancyInfo.Responsibilities
	}

	if vacancyInfo.ExtraInfo != nil {
		formVacancyInfo["Description"] = *vacancyInfo.ExtraInfo
	}

	formData, err := formgenerator.GenerateForm(formVacancyInfo)
	if err != nil {
		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
		h.logger.Error("error generating form",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	w.Header().Set("Content-Type", "text/html")

	if _, err := w.Write([]byte(formData)); err != nil {
		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
		h.logger.Error("error writing response: ",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}
}

func (h *handler) getEmployeesData(ctx context.Context, staff []string) ([]types.Employee, error) {
	employees := make([]types.Employee, len(staff))

	handlerName := "getEmployeesData"

	for i, uid := range staff {
		userInfo, err := h.authManager.GetUserInfo(ctx, uid)
		if err != nil {
			h.logger.Error("Error retrieving user info",
				slog.String("error", err.Error()),
				slog.String("source", handlerName))

			return nil, err
		}

		permissions, err := h.authManager.GetPermissionsByUsersId(ctx, uid)
		if err != nil {
			return nil, err
		}

		employees[i] = types.Employee{
			UID:         uid,
			FirstName:   userInfo.FirstName,
			LastName:    userInfo.SecondName,
			Email:       userInfo.Email,
			Permissions: h.extractPermissions(permissions),
		}

		if userInfo.Patronymic != nil {
			employees[i].MiddleName = *userInfo.Patronymic
		}
	}

	return employees, nil
}

func (h *handler) extractPermissions(permMap *auth.GetPermissionsByUsersIdResponse) map[constants.PermissionType]constants.PermissionValue {
	perm := map[constants.PermissionType]constants.PermissionValue{
		keycloak.PermissionProfile:     constants.PermissionValue(permMap.Profile),
		keycloak.PermissionEmployees:   constants.PermissionValue(permMap.Employees),
		keycloak.PermissionCompany:     constants.PermissionValue(permMap.Company),
		keycloak.PermissionVacancies:   constants.PermissionValue(permMap.Vacancies),
		keycloak.PermissionBalance:     constants.PermissionValue(permMap.Balance),
		keycloak.PermissionSubmissions: constants.PermissionValue(permMap.Submissions),
	}

	return perm
}

func (h *handler) getEmployeesHandler(w http.ResponseWriter, r *http.Request) {
	const handlerName = "getEmployeesHandler"

	var (
		uid       = r.URL.Query().Get("uid")
		vars      = mux.Vars(r)
		companyId = vars["company_id"]
	)

	// TODO: rewrite to request struct
	if uid == "" {
		http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
		h.logger.Error("UID is required",
			slog.String("handler", handlerName),
			slog.String("source", "getEmployeesHandler.checkUserPermissions"))

		return
	}

	company, err := h.distributorService.GetCompanyInfoById(r.Context(), &rmodel.CompanyByIdGetRequest{
		UserId:    uid,
		CompanyId: companyId,
	})
	if err != nil {
		h.handleDistributorError(w, err)
		h.logger.Error("error getting company info",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	employees, err := h.getEmployeesData(r.Context(), company.Company.Staff)
	if err != nil {
		h.handleKeycloakError(w, err)
		h.logger.Error("error getting employees data",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	response := rmodel.EmployeeResponse{
		CompanyID: companyId,
		Employees: employees,
	}

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

	if err = json.NewEncoder(w).Encode(response); 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))
	}
}

func (h *handler) getUserValidationStatusHandler(w http.ResponseWriter, r *http.Request) {
	const handlerName = "getValidationStatusHandler"

	var (
		vars = mux.Vars(r)
		uid  = vars["uid"]
	)

	if _, err := uuid.Parse(uid); err != nil {
		http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
		h.logger.Error("Invalid uid",
			slog.String("error", constants.ErrBadRequest.Error()),
			slog.String("handler", handlerName))

		return
	}

	resp, err := h.dbClient.GetClientValidation(r.Context(), &dbtypes.ClientValidationGetRequest{
		UserId: uid,
	})
	if err != nil {
		h.handleDBError(w, err)
		h.logger.Error("error getting user validation",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

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

	if err = json.NewEncoder(w).Encode(resp); 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))
	}
}

func (h *handler) getFileHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	fileName := vars["file"]

	if fileName == "" {
		http.Error(w, "file parameter is required", http.StatusBadRequest)

		return
	}

	link, err := h.cacheClient.Get(r.Context(), fileName, cache.DocumentsValueType)
	if err != nil && !errors.Is(err, cache.ErrKeyNotFound) {
		h.logger.Error("error getting file link from cache",
			slog.String("error", err.Error()))
	}

	if errors.Is(err, cache.ErrKeyNotFound) {
		newLink, err := h.objectStorageClient.GetPresignedLink(r.Context(),
			fileName, object_storage.DocumentCategory, object_storage.LinkOptions{
				TTL: ptr.Duration(time.Minute * 30),
			})
		if err != nil {
			if errors.Is(err, object_storage.ErrObjectNotFound) {
				http.Error(w, constants.ErrNotFound.Error(), http.StatusNotFound)

				return
			}

			h.logger.Error("error getting file link from object storage",
				slog.String("error", err.Error()))

			http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)

			return
		}

		body, err := json.Marshal(types.GetDocumentsResponse{
			Link:      newLink,
			ExpiresIn: time.Now().Add(time.Minute * 25),
		})
		if err != nil {
			http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
			return
		}

		if err := h.cacheClient.Set(r.Context(), fileName, cache.DocumentsValueType, string(body), time.Minute*25); err != nil {
			h.logger.Error("error setting file link to cache",
				slog.String("error", err.Error()))
		}

		link = string(body)
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte(link))
}

func (h *handler) verifyEmailHandler(w http.ResponseWriter, r *http.Request) {
	const handlerName = "verifyEmailHandler"

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

	if userID == "" || token == "" {
		http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
		h.logger.Error("missing parameters: uid or token",
			slog.String("handler", handlerName))

		return
	}

	if err := h.authManager.VerifyEmail(r.Context(), userID, token); err != nil {
		h.handleKeycloakError(w, err)
		h.logger.Error("error verifying email",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	userInfo, err := h.authManager.GetUserInfo(r.Context(), userID)
	if err != nil {
		h.logger.Error("error getting user info after verification",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))
		w.WriteHeader(http.StatusOK)

		return
	}

	userName := h.formatUserFullName(userInfo)

	if err := h.sendWelcomeEmail(userInfo.Email, userName); err != nil {
		h.logger.Error("error sending welcome email to user",
			slog.String("error", err.Error()),
			slog.String("user_id", userID),
			slog.String("handler", handlerName))
	}

	if err := h.sendNewUserNotificationToAdmin(userInfo, userName); err != nil {
		h.logger.Error("error sending new user notification to admin",
			slog.String("error", err.Error()),
			slog.String("user_id", userID),
			slog.String("handler", handlerName))
	}

	h.logger.Info("successfully sent new user notification to admin",
		slog.String("user_email", userInfo.Email),
		slog.String("user_type", getUserTypeName(userInfo.UserType)),
		slog.String("admin_email", constants.AdminNotificationEmail))

	w.WriteHeader(http.StatusOK)
}

func (h *handler) sendWelcomeEmail(email, userName string) error {
	msg, err := proto.Marshal(&notification.SendEmailRequest{
		To:          []string{email},
		Subject:     constants.RegistrationNotificationMessageSubject,
		ContentType: constants.TextNotificationContentType,
		Body: []byte(fmt.Sprintf(
			constants.RegistrationNotificationText,
			userName,
		)),
	})
	if err != nil {
		return fmt.Errorf("marshaling welcome email: %w", err)
	}

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

	h.logger.Info("successfully sent welcome email after verification",
		slog.String("user_email", email))

	return nil
}

func getUserTypeName(userType int32) string {
	switch userType {
	case keycloak.UserAgentType:
		return constants.UserTypeAgentName
	case keycloak.UserDistributorType:
		return constants.UserTypeDistributorName
	default:
		return fmt.Sprintf("Неизвестный тип (%d)", userType)
	}
}

func (h *handler) formatUserFullName(userInfo *auth.UserInfo) string {
	fullName := fmt.Sprintf("%s %s", userInfo.SecondName, userInfo.FirstName)
	if userInfo.Patronymic != nil && *userInfo.Patronymic != "" {
		fullName = fmt.Sprintf("%s %s", fullName, *userInfo.Patronymic)
	}

	return fullName
}

func (h *handler) sendNewUserNotificationToAdmin(userInfo *auth.UserInfo, userName string) error {
	userTypeName := getUserTypeName(userInfo.UserType)

	emailBody := fmt.Sprintf(
		constants.EmailNewUserRegistrationAdminMessage,
		userInfo.Email,
		userName,
		userTypeName,
	)

	msg, err := proto.Marshal(&notification.SendEmailRequest{
		SenderId:    constants.AdminNotificationId,
		To:          constants.AdminNotificationEmails,
		Subject:     constants.RegistrationNewUserAdmin,
		ContentType: constants.TextNotificationContentType,
		Body:        []byte(emailBody),
	})
	if err != nil {
		return fmt.Errorf("marshaling admin notification: %w", err)
	}

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

	return nil
}

func (h *handler) getEmailVerificationStatusHandler(w http.ResponseWriter, r *http.Request) {
	const handlerName = "getEmailVerificationStatusHandler"

	// Get user ID from query parameters
	userID := r.URL.Query().Get("uid")

	if userID == "" {
		http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
		h.logger.Error("missing parameter: uid",
			slog.String("handler", handlerName))

		return
	}

	// Check if email is verified
	response, err := h.authManager.GetEmailVerificationStatus(r.Context(), userID)
	if err != nil {
		h.handleKeycloakError(w, err)
		h.logger.Error("error getting email verification status",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

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

	if err := json.NewEncoder(w).Encode(response); 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))

		return
	}
}

func (h *handler) validateEmail(email string) (bool, error) {
	apiURL := fmt.Sprintf("%s?api_key=%s&email=%s",
		constants.EmailVerificationServiceURL,
		url.QueryEscape(h.emailVerificationServiceAPIKey),
		url.QueryEscape(email),
	)

	client := &http.Client{
		Timeout: 15 * time.Second,
	}

	req, err := http.NewRequestWithContext(
		context.Background(),
		http.MethodGet,
		apiURL,
		nil,
	)
	if err != nil {
		return false, fmt.Errorf("failed to create request: %w", err)
	}

	//стоит что-то такое добавить req.Header.Set("User-Agent", "MyAppSecurity/1.0")
	resp, err := client.Do(req)
	if err != nil {
		return false, fmt.Errorf("API request failed: %w", err)
	}
	defer resp.Body.Close()

	var result struct {
		Deliverability string `json:"deliverability"`
		QualityScore   string `json:"quality_score"`
		IsDisposable   struct {
			Value bool `json:"value"`
		} `json:"is_disposable_email"`
		IsMxFound struct {
			Value bool `json:"value"`
		} `json:"is_mx_found"`
		IsSmtpValid struct {
			Value bool `json:"value"`
		} `json:"is_smtp_valid"`
	}

	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return false, fmt.Errorf("failed to parse validation response: %w", err)
	}

	qualityScore, err := strconv.ParseFloat(result.QualityScore, 64)
	if err != nil {
		h.logger.Warn("could not parse quality score, ignoring in validation",
			slog.String("quality_score", result.QualityScore))

		qualityScore = 0
	}

	isValidEmail := result.Deliverability == "DELIVERABLE" && // Доставляемость
		!result.IsDisposable.Value && // Не одноразовый адрес
		result.IsMxFound.Value && // Есть MX-записи DNS
		result.IsSmtpValid.Value && // Сервер отвечает на SMTP
		qualityScore >= 0.7 // Высокий общий показатель качества

	h.logger.Debug("email validation details",
		slog.String("email", email),
		slog.String("deliverability", result.Deliverability),
		slog.String("quality_score", result.QualityScore),
		slog.Bool("is_disposable", result.IsDisposable.Value),
		slog.Bool("is_mx_found", result.IsMxFound.Value),
		slog.Bool("is_smtp_valid", result.IsSmtpValid.Value),
		slog.Bool("is_valid_result", isValidEmail))

	return isValidEmail, nil
}

func (h *handler) GetUserEventsHandler(w http.ResponseWriter, r *http.Request) {
	handlerName := "GetUserEventsHandler"
	query := r.URL.Query()
	vars := mux.Vars(r)
	ownerID := vars["uid"]

	ownerType := query.Get("user_type")
	if ownerType == "" {
		h.logger.Error("Missing required query parameter",
			slog.String("parameter", "user_type"),
			slog.String("handler", handlerName))
		http.Error(w, "Missing required query parameter: type", http.StatusBadRequest)

		return
	}

	h.logger.Debug("Request received",
		slog.String("handler", handlerName),
		slog.String("userID", ownerID),
		slog.Any("queryParams", query))

	eventTypesStr := query.Get("event_type")

	eventTypesSlice := strings.Split(eventTypesStr, ",")

	eventTypes := make([]feed.EventType, len(eventTypesStr))
	for i, s := range eventTypesSlice {
		eventTypes[i] = feed.EventType(s)
	}

	filter := feed.Filter{
		OwnerID:       ownerID,
		EventTypes:    eventTypes,
		ShowCancelled: query.Get("show_cancelled") != "false",
	}

	if limitStr := query.Get("limit"); limitStr != "" {
		limit, err := strconv.ParseUint(limitStr, 10, 64)
		if err == nil && limit > 0 {
			filter.Limit = limit
		} else {
			h.logger.Error("Invalid limit parameter",
				slog.String("handler", handlerName),
				slog.String("userID", ownerID),
				slog.String("invalidValue", limitStr),
				slog.String("error", "invalid integer format or value"))
			http.Error(w, "Invalid limit parameter", http.StatusBadRequest)

			return
		}
	} else {
		filter.Limit = constants.DefaultFeedFilterLimit
	}

	if offsetStr := query.Get("offset"); offsetStr != "" {
		offset, err := strconv.ParseUint(offsetStr, 10, 64)
		if err == nil {
			filter.Offset = offset
		} else {
			h.logger.Error("Invalid offset parameter",
				slog.String("handler", handlerName),
				slog.String("userID", ownerID),
				slog.String("invalidValue", offsetStr),
				slog.String("error", "invalid integer format or value"))
			http.Error(w, "Invalid offset parameter", http.StatusBadRequest)

			return
		}
	} else {
		filter.Offset = 0
	}

	events, err := h.feed.Service.GetUserEvents(r.Context(), filter, ownerType)
	if err != nil {
		h.logger.Error("Error getting events",
			slog.String("handler", handlerName),
			slog.String("userID", ownerID),
			slog.String("error", err.Error()))
		http.Error(w, "Failed to retrieve events", http.StatusInternalServerError)

		return
	}

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

	if err := json.NewEncoder(w).Encode(events); err != nil {
		h.logger.Error("Error encoding response",
			slog.String("handler", handlerName),
			slog.String("userID", ownerID),
			slog.String("error", err.Error()))
	}
}
