package http_router

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

	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/integration"
	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/types"
	"github.com/gorilla/mux"

	filemanager "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/file_manager"
	rmodel "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/request_model"
)

// @Summary Получить список заявок агента
// @Description Получение списка заявок агента с возможностью фильтрации
// @Tags agents
// @Accept json
// @Produce json
// @Param agent_id path string true "ID агента"
// @Param vacancy_id query string false "ID вакансии"
// @Param status query string false "Статус заявки"
// @Param page query int false "Номер страницы"
// @Param page_size query int false "Размер страницы"
// @Success 200 {object} rmodel.SubmissionListGetResponse "Список заявок"
// @Failure 400 {object} map[string]string "Неверные данные запроса"
// @Failure 401 {object} map[string]string "Неавторизованный доступ"
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
// @Security BearerAuth
// @Router /api/v1/agents/{agent_id}/submissions [get]
func (h *handler) getSubmissionListAgentHandler(w http.ResponseWriter, r *http.Request) {
	const handlerName = "getSubmissionListAgentHandler"

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

	request, err := new(rmodel.SubmissionListGetRequest).FromQuery(r.URL.Query())
	if err != nil {
		http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
		h.logger.Error("error parsing request: ",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	request.AgentId = agentId

	result, err := h.agentService.GetSubmissionList(r.Context(), request)
	if err != nil {
		h.handleAgentError(w, err)
		h.logger.Error("error getting submission list",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName),
		)
	}

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

	if err := json.NewEncoder(w).Encode(result); 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 Получить CV заявки
// @Description Получение CV файла заявки
// @Tags agents
// @Accept json
// @Produce application/octet-stream
// @Param agent_id path string true "ID агента"
// @Param submission_id path string true "ID заявки"
// @Success 200 {file} file "CV файл"
// @Failure 400 {object} map[string]string "Неверные данные запроса"
// @Failure 401 {object} map[string]string "Неавторизованный доступ"
// @Failure 404 {object} map[string]string "CV не найден"
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
// @Security BearerAuth
// @Router /api/v1/agents/{agent_id}/submissions/{submission_id}/cv [get]
func (h *handler) getSubmissionCVHandler(w http.ResponseWriter, r *http.Request) {
	const handlerName = "getSubmissionCVHandler"

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

	params := filemanager.ParameterTable{
		filemanager.SubmissionIdParam: submissionId,
	}

	urls, err := h.fileManager.GetFilePaths(r.Context(), filemanager.CVFileType, params)
	if err != nil {
		if errors.Is(err, filemanager.ErrFileNotFound) {
			http.Error(w, constants.ErrNotFound.Error(), http.StatusNotFound)
			h.logger.Error("CV file not found",
				slog.String("error", err.Error()),
				slog.String("submissionId", submissionId),
				slog.String("handler", handlerName))

			return
		}

		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
		h.logger.Error("error while getting file URLs: ",
			slog.String("error", err.Error()),
			slog.String("submissionId", submissionId),
			slog.String("handler", handlerName))

		return
	}

	if len(urls) == 0 {
		http.Error(w, constants.ErrNotFound.Error(), http.StatusNotFound)
		h.logger.Error("No CV URLs found",
			slog.String("submissionId", submissionId),
			slog.String("handler", handlerName))

		return
	}

	response := types.CVURLResponse{
		URL:          urls[0],
		SubmissionID: submissionId,
		ExpiresAt:    time.Now().Add(constants.DefaultFileTTL - time.Hour),
	}

	h.logger.Debug("CV URL retrieved successfully",
		slog.String("submissionId", submissionId),
		slog.String("handler", handlerName))

	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("submissionId", submissionId),
			slog.String("handler", handlerName))
	}
}

// @Summary Удалить заявку агента
// @Description Удаление заявки агента
// @Tags agents
// @Accept json
// @Produce json
// @Param agent_id path string true "ID агента"
// @Param submission_id path string true "ID заявки"
// @Success 204 "Заявка удалена"
// @Failure 400 {object} map[string]string "Неверные данные запроса"
// @Failure 401 {object} map[string]string "Неавторизованный доступ"
// @Failure 404 {object} map[string]string "Заявка не найдена"
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
// @Security BearerAuth
// @Router /api/v1/agents/{agent_id}/submissions/{submission_id} [delete]
func (h *handler) deleteSubmissionAgentHandler(w http.ResponseWriter, r *http.Request) {
	const handlerName = "deleteSubmissionHandler"

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

	result, err := h.agentService.DeleteSubmission(r.Context(), &rmodel.SubmissionDeleteRequest{
		Id: submissionId,
	})
	if err != nil {
		h.handleAgentError(w, err)
		h.logger.Error("error deleting submission",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName),
		)

		return
	}

	go func() {
		if err := h.feed.CancelEvent(r.Context(), submissionId, "Отклик был удален"); err != nil {
			http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
			h.logger.Error("error cancelling event: ",
				slog.String("error", err.Error()),
				slog.String("handler", handlerName))
		}
	}()

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

	if err := json.NewEncoder(w).Encode(result); 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 Получение списка заявок дистрибьютора с возможностью фильтрации
// @Tags distributors
// @Accept json
// @Produce json
// @Param distributor_id path string true "ID дистрибьютора"
// @Param vacancy_id path string true "ID вакансии"
// @Param status query string false "Статус заявки"
// @Param page query int false "Номер страницы"
// @Param page_size query int false "Размер страницы"
// @Success 200 {object} rmodel.SubmissionListGetResponse "Список заявок"
// @Failure 400 {object} map[string]string "Неверные данные запроса"
// @Failure 401 {object} map[string]string "Неавторизованный доступ"
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
// @Security BearerAuth
// @Router /api/v1/distributor/{distributor_id}/vacancies/{vacancy_id}/submissions [get]
func (h *handler) getSubmissionListDistributorHandler(w http.ResponseWriter, r *http.Request) {
	const handlerName = "getSubmissionsDistributorHandler"

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

	request, err := new(rmodel.SubmissionListForVacancyGetRequest).FromQuery(r.URL.Query())
	if err != nil {
		http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
		h.logger.Error("error parsing request: ",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName),
		)

		return
	}

	request.VacancyId = vacancyId

	result, err := h.distributorService.GetSubmissionListForVacancy(r.Context(), request)
	if err != nil {
		h.handleDistributorError(w, err)
		h.logger.Error("error getting submissions for vacancy",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName),
		)

		return
	}

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

	if err := json.NewEncoder(w).Encode(result); 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 Обновление статуса заявки дистрибьютором
// @Tags distributors
// @Accept json
// @Produce json
// @Param distributor_id path string true "ID дистрибьютора"
// @Param vacancy_id path string true "ID вакансии"
// @Param submission_id path string true "ID заявки"
// @Param request body rmodel.SubmissionStatusUpdateRequest true "Новый статус заявки"
// @Success 200 "Статус обновлен"
// @Failure 400 {object} map[string]string "Неверные данные запроса"
// @Failure 401 {object} map[string]string "Неавторизованный доступ"
// @Failure 404 {object} map[string]string "Заявка не найдена"
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
// @Security BearerAuth
// @Router /api/v1/distributor/{distributor_id}/vacancies/{vacancy_id}/submissions/{submission_id}/status [post]
func (h *handler) updateSubmissionStatusDistributorHandler(w http.ResponseWriter, r *http.Request) {
	const handlerName = "updateSubmissionStatusDistributorHandler"

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

	var request rmodel.SubmissionStatusUpdateRequest

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

		return
	}

	request.Id = submissionId

	if _, err := h.distributorService.UpdateSubmissionStatus(r.Context(), &request); err != nil {
		h.handleDistributorError(w, err)
		h.logger.Error("error updating submission status",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName),
		)

		return
	}

	// TODO:
	// h.createSetSubmissionStatusFeedEvent(r.Context(), distId, submissionId, status.Status, handlerName)

	w.WriteHeader(http.StatusNoContent)
}

// @Summary Отправить анкету
// @Description Отправка заполненной анкеты клиента
// @Tags clients
// @Accept json
// @Produce json
// @Param request body map[string]interface{} true "Данные анкеты"
// @Success 201 {object} map[string]string "Анкета отправлена"
// @Failure 400 {object} map[string]string "Неверные данные запроса"
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
// @Router /api/v1/anketa [post]
//
//nolint:funlen // TODO: refactor
func (h *handler) postAnketaHandler(w http.ResponseWriter, r *http.Request) {
	handlerName := "postAnketaHandler"

	var (
		agentID   = r.FormValue("agent_id")
		vacancyID = r.FormValue("vacancy_id")
		cvLink    = r.FormValue("cv")
		name      = r.FormValue("name")
		phone     = r.FormValue("phone_number")
		email     = r.FormValue("email")
		birthday  = r.FormValue("birthday")
		info      = r.FormValue("info")

		firstName, lastName, middleName = splitName(name)
	)

	result, err := h.agentService.CreateSubmission(r.Context(), &rmodel.SubmissionCreateRequest{
		AgentId:   agentID,
		VacancyId: vacancyID,
		CandidateInfo: &rmodel.CandidateInfo{
			FirstName:   firstName,
			LastName:    lastName,
			MiddleName:  middleName,
			PhoneNumber: phone,
			Email:       email,
			Birthday:    birthday,
			CvLink:      &cvLink,
			Resume:      &info,
		},
	})
	if err != nil {
		h.handleAgentError(w, err)
		h.logger.Error("error creating submission",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))

		return
	}

	file, header, err := r.FormFile("cv_file")
	if err == nil {
		params := filemanager.ParameterTable{
			filemanager.SubmissionIdParam: result.Id,
		}

		if err := h.fileManager.SaveFile(
			r.Context(),
			filemanager.CVFileType,
			file,
			header,
			params,
		); err != nil {
			http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
			h.logger.Error("error saving CV to file storage: ",
				slog.String("error", err.Error()),
				slog.String("handler", handlerName))

			return
		}
	} else if header != nil {
		// CV is provided, but there are other errors
		http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
		h.logger.Error("error while extracting CV from request: ",
			slog.String("error", err.Error()),
			slog.String("handler", handlerName))
	}

	go h.createPostAnketaEvent(
		context.Background(),
		agentID,
		result.Id,
		handlerName,
	)

	go func() {
		if err := h.integrationClient.HandleVacancyResponse(integration.HandleVacancy{
			AgentId:   agentID,
			VacancyId: vacancyID,
			SourceLid: r.URL.RawPath,
			Candidate: integration.Candidate{
				Id:       result.Id,
				Name:     name,
				Email:    email,
				Phone:    phone,
				Birthday: birthday,
				Info:     info,
				CvLink:   cvLink,
			},
		}); err != nil {
			h.logger.Error("error while handling vacancy response integration",
				slog.String("error", err.Error()),
				slog.String("agent_id", agentID),
				slog.String("vacancy_id", vacancyID),
				slog.String("submission_id", result.Id),
				slog.String("handler", handlerName),
			)
		}
	}()

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

	if err = json.NewEncoder(w).Encode(result); 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 splitName(name string) (string, string, string) {
	var (
		parts                           = strings.SplitN(name, " ", 3)
		lastName, firstName, middleName string
	)

	if len(parts) > 0 {
		lastName = parts[0]
	}

	if len(parts) > 1 {
		firstName = parts[1]
	}

	if len(parts) > 2 {
		middleName = parts[2]
	}

	return lastName, firstName, middleName
}
