package integration

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

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

const (
	vkusvillMolvaUtmSource = "molva"

	dmYFormat = "02.01.2006" // datetime format stored in DB
	YmdFormat = "2006-01-02" // datetime format used in vkusvill
)

var (
	// TODO: use reflect to set json tags
	vkusvillVacancyIdFieldKey = "vacancy_id"

	vkusvillNeededFields = map[string]struct{}{
		vkusvillVacancyIdFieldKey: {},
	}
)

type (
	VkusvillSecretsConfig struct {
		ApiToken string
	}
)

type (
	vkusvillSourceLid struct {
		Url string `json:"url"`
	}

	vkusvillSaveCandidateRequest struct {
		Status     int               `json:"status"`      // see status mapping
		LastName   string            `json:"last_name"`   // required
		Name       string            `json:"name"`        // required
		SecondName string            `json:"second_name"` // required
		Birthday   string            `json:"birthday"`    // YYYY-mm-dd
		Phone      []string          `json:"phone"`
		VacancyId  int               `json:"vacancy_id"` // see vacancy to id mapping
		CityJob    string            `json:"city_job"`   // unsupported now
		MetroArea  string            `json:"metro_area"` // unsupported now
		UtmSource  string            `json:"utm_source"`
		SourceLid  vkusvillSourceLid `json:"source_lid"`
	}
)

func (c *client) handleVkusvillIntegration(
	ctx context.Context,
	request HandleVacancy,
	vacancyExtraFields *VacancyExtraFields,
	companyMetadata *CompanyMetadata,
	companyExtraFieldsTemplate *CompanyExtraFieldsTemplate,
) error {
	host, ok := (*companyMetadata)["host"]
	if !ok {
		return fmt.Errorf("host not found in metadata")
	}

	method, ok := (*companyMetadata)["method"]
	if !ok {
		return fmt.Errorf("request method not found in metadata")
	}

	lastName, name, secondName := splitName(request.Candidate.Name)

	extraFields, err := parseVacancyExtraFields(vacancyExtraFields, companyExtraFieldsTemplate)
	if err != nil {
		return fmt.Errorf("error extracting vacancy id: %w", err)
	}

	vacancyId, ok := extraFields[vkusvillVacancyIdFieldKey].(int)
	if !ok {
		return fmt.Errorf("vacancy id field is not of expected type (int)")
	}

	sourceUrl, err := url.Parse(request.SourceLid)
	if err != nil {
		return fmt.Errorf("error parsing source lid: %w", err)
	}

	query := sourceUrl.Query()
	// TODO: think what we potentially want (maybe request id for tracing etc.)
	query.Set("candidate_id", request.Candidate.Id)

	sourceUrl.RawQuery = query.Encode()

	saveCandidateReq := vkusvillSaveCandidateRequest{
		Status:     0,
		LastName:   lastName,
		Name:       name,
		SecondName: secondName,
		Birthday:   formatDate(request.Candidate.Birthday, dmYFormat, YmdFormat),
		Phone:      []string{request.Candidate.Phone},
		VacancyId:  vacancyId,
		CityJob:    "",
		MetroArea:  "",
		UtmSource:  vkusvillMolvaUtmSource,
		SourceLid: vkusvillSourceLid{
			Url: sourceUrl.String(),
		},
	}

	resp, err := c.vkusvillSaveCandidate(method, host, &saveCandidateReq)
	if err != nil {
		return fmt.Errorf("error making request: %w", err)
	}

	defer resp.Body.Close()

	// TODO: think about following:
	// у Вкусвилла ошибки возвращаются в теле ответа. Соответственно, нужно
	// а) проверить наличие ошибки
	// б) если ошибка есть, создать алерт об этом (например, в админку). Вообще надо подумать над этим сценарием

	return c.vkusvillHandleResponse(resp)
}

type (
	VkusvillSaveCandidateResponse struct {
		//nolint:tagliatelle // used in vkusvill
		Error bool `json:"ERR"`
		//nolint:tagliatelle // used in vkusvill
		Data json.RawMessage `json:"DATA"`
	}

	VkusvillErrorMessage struct {
		Field   string `json:"field"`
		Message string `json:"message"`
	}

	VkusvillErrorDetails struct {
		Data []VkusvillErrorMessage
	}

	VkusvillSuccessDetails struct {
		// TODO: maybe add more fields
	}
)

func (r *VkusvillSaveCandidateResponse) GetErrorDetails() (*VkusvillErrorDetails, error) {
	var errDetails VkusvillErrorDetails

	if err := json.Unmarshal(r.Data, &errDetails); err != nil {
		return nil, err
	}

	return &errDetails, nil
}

func (r *VkusvillSaveCandidateResponse) GetSuccessDetails() (*VkusvillSuccessDetails, error) {
	var successDetails VkusvillSuccessDetails

	if err := json.Unmarshal(r.Data, &successDetails); err != nil {
		return nil, err
	}

	return &successDetails, nil
}

func (c *client) vkusvillHandleResponse(resp *http.Response) error {
	const handlerName = "integration.vkusvillHandleResponse"

	var vkusvillResp VkusvillSaveCandidateResponse

	if err := json.NewDecoder(resp.Body).Decode(&vkusvillResp); err != nil {
		return fmt.Errorf("error decoding vkusvill response: %w", err)
	}

	if vkusvillResp.Error {
		errDetails, err := vkusvillResp.GetErrorDetails()
		if err != nil {
			return fmt.Errorf("error getting vkusvill error details: %w", err)
		}

		return fmt.Errorf("error saving candidate in vkusvill HRM: %v", errDetails)
	}

	successDetails, err := vkusvillResp.GetSuccessDetails()
	if err != nil {
		return fmt.Errorf("error getting vkusvill success details: %w", err)
	}

	// TODO: remove after testing
	c.logger.Debug("vkusvill response body",
		slog.String("body", string(vkusvillResp.Data)),
		slog.String("handler", handlerName))

	c.logger.Debug("vkusvill success details",
		slog.Any("success_details", successDetails),
		slog.String("handler", handlerName))

	return nil
}

func splitName(name string) (string, string, string) {
	splittedName := strings.SplitN(name, " ", 3)

	var (
		lastName, firstName, secondName string
	)

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

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

	if len(splittedName) > 3 {
		secondName = splittedName[2]
	}

	return lastName, firstName, secondName
}

func formatDate(date, fromFmt, toFmt string) string {
	parsed, err := time.Parse(fromFmt, date)
	if err != nil {
		return date
	}

	return parsed.Format(toFmt)
}

//nolint:gocognit // TODO: refactor
func parseVacancyExtraFields(
	vacancyExtraFields *VacancyExtraFields,
	companyExtraFieldsTemplate *CompanyExtraFieldsTemplate,
) (map[string]any, error) {
	resMap := make(map[string]any)

	for _, vacXField := range vacancyExtraFields.Fields {
		if _, ok := vkusvillNeededFields[vacXField.SystemName]; !ok {
			continue
		}

		if len(vacXField.Value) == 0 {
			// TODO: Maybe here we need to check whether corresponding field template
			// has required flag set to true
			continue
		}

		if vacXField.IsValidValue {
			// get corresponding value from company template
			for _, comFieldTemplate := range companyExtraFieldsTemplate.Fields {
				if comFieldTemplate.SystemName != vacXField.SystemName {
					continue
				}

				if vacXField.MultipleChoice {
					var (
						nameValMap = make(map[string]string, len(comFieldTemplate.ValidValues))
						res        = make([]any, len(vacXField.Value))
						err        error
					)

					for _, validVal := range comFieldTemplate.ValidValues {
						nameValMap[validVal.DisplayName] = validVal.Value
					}

					for i, val := range vacXField.Value {
						res[i], err = convertFieldtype(vacXField.Type, nameValMap[val])
						if err != nil {
							return nil, fmt.Errorf("error converting field value: %w", err)
						}
					}

					resMap[vacXField.SystemName] = res
				} else {
					for _, validVal := range comFieldTemplate.ValidValues {
						if validVal.DisplayName != vacXField.Value[0] {
							continue
						}

						res, err := convertFieldtype(vacXField.Type, validVal.Value)
						if err != nil {
							return nil, fmt.Errorf("error converting field value: %w", err)
						}

						resMap[vacXField.SystemName] = res
					}
				}
			}

			continue
		}

		if vacXField.MultipleChoice {
			var (
				res = make([]any, len(vacXField.Value))
				err error
			)

			for i, v := range vacXField.Value {
				res[i], err = convertFieldtype(vacXField.Type, v)
				if err != nil {
					return nil, fmt.Errorf("error converting field value: %w", err)
				}
			}

			resMap[vacXField.SystemName] = res
		} else {
			res, err := convertFieldtype(vacXField.Type, vacXField.Value[0])
			if err != nil {
				return nil, fmt.Errorf("error converting field value: %w", err)
			}

			resMap[vacXField.SystemName] = res
		}
	}

	return resMap, nil
}

func VkusvillGetCandidateStatus(status int) (rmodel.SubmissionStatus, error) {
	switch status {
	case 0: // Статус не установлен
		return rmodel.SubStatusUnspecified, nil
	case 666: // Архивный
		return rmodel.SubStatusCancelled, nil
	case 49: // Без вакансии
		return rmodel.SubStatusCancelled, nil
	case 50: // На рассмотрении
		return rmodel.SubStatusOnInterview, nil
	case 51: // Отклоненный
		return rmodel.SubStatusRejected, nil
	case 115: // Зарезервированный
		return rmodel.SubStatusApproved, nil
	default:
		return rmodel.SubStatusUnspecified, ErrUnknownCandidateStatus
	}
}
