package requestmodel

import (
	"fmt"
	"math"
	"net/url"
	"strconv"
	"time"

	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
	dbtypes "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/types"
)

type VacancyStatus string

const (
	VacUnspecified VacancyStatus = "unspecified"
	VacNew         VacancyStatus = "new"
	VacPending     VacancyStatus = "pending"
	VacApproved    VacancyStatus = "approved"
	VacRejected    VacancyStatus = "rejected"
)

func (s *VacancyStatus) NullString() *string {
	if s == nil {
		return nil
	}

	return (*string)(s)
}

type (
	// TODO: remove extra fields for agent
	Vacancy struct {
		Id                 string              `json:"id"`
		Company            VacancyCompanyInfo  `json:"company"`
		Name               string              `json:"name"`
		Address            string              `json:"address"`
		WorkFormat         string              `json:"work_format"`
		AgentReward        *int32              `json:"agent_reward"`
		SalaryTop          int32               `json:"salary_top"`
		SalaryBottom       int32               `json:"salary_bottom"`
		Requirements       *string             `json:"requirements"`
		Responsibilities   *string             `json:"responsibilities"`
		ExtraInfo          *string             `json:"extra_info"`
		Region             string              `json:"region"`
		IsArchived         bool                `json:"is_archived"`
		TargetAction       VacancyTargetAction `json:"target_action"`
		Moderation         VacancyModeration   `json:"moderation"`
		RequiredCandidates int32               `json:"required_candidates"`
		CurrentCandidates  int32               `json:"current_candidates"`
		ExtraFields        *string             `json:"extra_fields"`
		CreatedAt          string              `json:"created_at"`
	}

	VacancyCompanyInfo struct {
		Id       string  `json:"id"`
		Name     string  `json:"name"`
		LogoLink *string `json:"logo_link"`
	}

	VacancyTargetAction struct {
		Action   string `json:"action"`
		Duration int32  `json:"duration"`
	}

	VacancyModeration struct {
		Status             VacancyStatus                  `json:"status"`
		DescriptionHistory []VacancyModerationDescription `json:"description_history"`
	}

	VacancyModerationDescription struct {
		Description *string `json:"description"`
		CreatedAt   string  `json:"created_at"`
	}
)

func (m *VacancyModeration) From(msg *dbtypes.VacancyModeration) *VacancyModeration {
	if msg == nil {
		return nil
	}

	res := &VacancyModeration{
		Status:             VacancyStatus(msg.Status),
		DescriptionHistory: make([]VacancyModerationDescription, len(msg.DescriptionHistory)),
	}

	for i, desc := range msg.DescriptionHistory {
		res.DescriptionHistory[i] = VacancyModerationDescription{
			Description: desc.Description,
			CreatedAt:   desc.CreatedAt.Format(time.DateOnly),
		}
	}

	return res
}

func (v *Vacancy) From(msg *dbtypes.Vacancy) *Vacancy {
	if msg == nil {
		return nil
	}

	res := &Vacancy{
		Id: msg.Id,
		Company: VacancyCompanyInfo{
			Id:   msg.Company.Id,
			Name: msg.Company.Name,
		},
		Name:             msg.Name,
		Address:          msg.Address,
		WorkFormat:       msg.WorkFormat,
		AgentReward:      msg.AgentReward,
		SalaryTop:        msg.SalaryTop,
		SalaryBottom:     msg.SalaryBottom,
		Requirements:     msg.Requirements,
		Responsibilities: msg.Responsibilities,
		ExtraInfo:        msg.ExtraInfo,
		Region:           msg.Region,
		IsArchived:       msg.IsArchived,
		TargetAction: VacancyTargetAction{
			Action:   msg.TargetAction.Action,
			Duration: msg.TargetAction.Duration,
		},
		Moderation:         *new(VacancyModeration).From(&msg.Moderation),
		RequiredCandidates: msg.RequiredCandidates,
		CurrentCandidates:  msg.CurrentCandidates,
		ExtraFields:        msg.ExtraFields,
		CreatedAt:          msg.CreatedAt.Format(time.DateOnly),
	}

	return res
}

type (
	VacancyListGetRequest struct {
		Filters  *VacancyListFilters
		Page     uint64
		PageSize uint64
	}

	VacancyListFilters struct {
		DistributorId *string
		CompanyId     *string
		VacancyId     *string
		Region        *string
		SalaryBottom  *int32
		SalaryTop     *int32
		IsArchived    *bool
		Status        *VacancyStatus
	}

	VacancyListGetResponse struct {
		Vacancies []Vacancy `json:"vacancies"`
	}
)

func (f *VacancyListGetRequest) FromQuery(query url.Values) (*VacancyListGetRequest, error) {
	if query == nil {
		return nil, nil
	}

	var filters VacancyListFilters

	if val, ok := query["distributor_id"]; ok {
		filters.DistributorId = &val[0]
	}

	if val, ok := query["company_id"]; ok {
		filters.CompanyId = &val[0]
	}

	if val, ok := query["vacancy_id"]; ok {
		filters.VacancyId = &val[0]
	}

	if val, ok := query["region"]; ok {
		filters.Region = &val[0]
	}

	if val, ok := query["salary_bottom"]; ok {
		intVal, err := parseInt32(val[0])
		if err != nil {
			return nil, fmt.Errorf("invalid salary_bottom param: %w", err)
		}

		filters.SalaryBottom = &intVal
	}

	if val, ok := query["salary_top"]; ok {
		intVal, err := parseInt32(val[0])
		if err != nil {
			return nil, fmt.Errorf("invalid salary_top param: %w", err)
		}

		filters.SalaryTop = &intVal
	}

	if val, ok := query["is_archived"]; ok {
		flag := val[0] == "true" || val[0] == "1"
		filters.IsArchived = &flag
	}

	if val, ok := query["status"]; ok {
		status := VacancyStatus(val[0])
		filters.Status = &status
	}

	res := &VacancyListGetRequest{
		Filters:  &filters,
		Page:     constants.DefaultPaginationPage,
		PageSize: constants.DefaultPaginationPageSize,
	}

	if val, ok := query["page"]; ok {
		intVal, err := parseUint64(val[0])
		if err != nil {
			return nil, fmt.Errorf("invalid page param: %w", err)
		}

		res.Page = intVal
	}

	if val, ok := query["page_size"]; ok {
		intVal, err := parseUint64(val[0])
		if err != nil {
			return nil, fmt.Errorf("invalid page_size param: %w", err)
		}

		res.PageSize = intVal
	}

	return res, nil
}

func (r *VacancyListGetRequest) Validate() error {
	if r == nil {
		return fmt.Errorf("%w: request is nil", ErrValidation)
	}

	if r.Filters != nil {
		if r.Filters.Status != nil && !(*r.Filters.Status == VacUnspecified ||
			*r.Filters.Status == VacNew ||
			*r.Filters.Status == VacPending ||
			*r.Filters.Status == VacApproved ||
			*r.Filters.Status == VacRejected) {
			return fmt.Errorf("%w: invalid status: %v", ErrValidation, *r.Filters.Status)
		}
	}

	return nil
}

func (r *VacancyListGetResponse) From(msg *dbtypes.VacancyListGetResponse) *VacancyListGetResponse {
	if msg == nil {
		return nil
	}

	res := &VacancyListGetResponse{
		Vacancies: make([]Vacancy, len(msg.Vacancies)),
	}

	for i, vacancy := range msg.Vacancies {
		res.Vacancies[i] = *new(Vacancy).From(&vacancy)
	}

	return res
}

type (
	VacancyCreateRequest struct {
		CompanyId          string              `json:"company_id"`
		Name               string              `json:"name"`
		Address            string              `json:"address"`
		WorkFormat         string              `json:"work_format"`
		AgentReward        *int32              `json:"agent_reward"`
		SalaryTop          int32               `json:"salary_top"`
		SalaryBottom       int32               `json:"salary_bottom"`
		Requirements       *string             `json:"requirements"`
		Responsibilities   *string             `json:"responsibilities"`
		ExtraInfo          *string             `json:"extra_info"`
		Region             string              `json:"region"`
		TargetAction       VacancyTargetAction `json:"target_action"`
		RequiredCandidates int32               `json:"required_candidates"`
		CurrentCandidates  int32               `json:"current_candidates"`
		ExtraFields        *string             `json:"extra_fields"`
	}

	VacancyCreateResponse struct {
		Id string `json:"id"`
	}
)

func (r *VacancyCreateRequest) Validate() error {
	if r == nil {
		return fmt.Errorf("%w: request is nil", ErrValidation)
	}

	if r.CompanyId == "" {
		return fmt.Errorf("%w: CompanyID is required", ErrValidation)
	}

	if r.Name == "" {
		return fmt.Errorf("%w: Name is required", ErrValidation)
	}

	if r.Address == "" {
		return fmt.Errorf("%w: Address is required", ErrValidation)
	}

	if r.WorkFormat == "" {
		return fmt.Errorf("%w: WorkFormat is required", ErrValidation)
	}

	if r.Region == "" {
		return fmt.Errorf("%w: Region is required", ErrValidation)
	}

	if r.TargetAction.Action == "" {
		return fmt.Errorf("%w: TargetAction.Action is required", ErrValidation)
	}

	if r.TargetAction.Duration == 0 {
		return fmt.Errorf("%w: TargetAction.Duration is required", ErrValidation)
	}

	return nil
}

func (r *VacancyCreateResponse) From(msg *dbtypes.VacancyCreateResponse) *VacancyCreateResponse {
	if msg == nil {
		return nil
	}

	return &VacancyCreateResponse{
		Id: msg.Id,
	}
}

type (
	VacancyUpdateRequest struct {
		Id                 string
		Name               *string                      `json:"name"`
		Address            *string                      `json:"address"`
		WorkFormat         *string                      `json:"work_format"`
		AgentReward        *int32                       `json:"agent_reward"`
		SalaryTop          *int32                       `json:"salary_top"`
		SalaryBottom       *int32                       `json:"salary_bottom"`
		Requirements       *string                      `json:"requirements"`
		Responsibilities   *string                      `json:"responsibilities"`
		ExtraInfo          *string                      `json:"extra_info"`
		Region             *string                      `json:"region"`
		TargetAction       VacancyTargetActionForUpdate `json:"target_action"`
		RequiredCandidates *int32                       `json:"required_candidates"`
		ExtraFields        *string                      `json:"extra_fields"`
	}

	VacancyTargetActionForUpdate struct {
		Action   *string `json:"action"`
		Duration *int32  `json:"duration"`
	}

	VacancyUpdateResponse struct {
	}
)

func (r *VacancyUpdateRequest) Validate() error {
	if r == nil {
		return fmt.Errorf("%w: request is nil", ErrValidation)
	}

	if r.Id == "" {
		return fmt.Errorf("%w: ID is required", ErrValidation)
	}

	if r.Name == nil && r.Address == nil && r.WorkFormat == nil && r.AgentReward == nil &&
		r.SalaryTop == nil && r.SalaryBottom == nil && r.Requirements == nil && r.Responsibilities == nil &&
		r.ExtraInfo == nil && r.Region == nil && r.TargetAction.Action == nil && r.TargetAction.Duration == nil &&
		r.RequiredCandidates == nil && r.ExtraFields == nil {
		return fmt.Errorf("%w: nothing to update", ErrValidation)
	}

	return nil
}

func (r *VacancyUpdateResponse) From(msg *dbtypes.VacancyUpdateResponse) *VacancyUpdateResponse {
	if msg == nil {
		return nil
	}

	return &VacancyUpdateResponse{}
}

type (
	VacancyDeleteRequest struct {
		Id string
	}

	VacancyDeleteResponse struct {
	}
)

func (r *VacancyDeleteRequest) Validate() error {
	if r == nil {
		return fmt.Errorf("%w: request is nil", ErrValidation)
	}

	if r.Id == "" {
		return fmt.Errorf("%w: ID is required", ErrValidation)
	}

	return nil
}

func (r *VacancyDeleteResponse) From(msg *dbtypes.VacancyDeleteResponse) *VacancyDeleteResponse {
	if msg == nil {
		return nil
	}

	return &VacancyDeleteResponse{}
}

type (
	SendVacancyToModerationRequest struct {
		Id string
	}

	SendVacancyToModerationResponse struct {
	}
)

func (r *SendVacancyToModerationRequest) Validate() error {
	if r == nil {
		return fmt.Errorf("%w: request is nil", ErrValidation)
	}

	if r.Id == "" {
		return fmt.Errorf("%w: ID is required", ErrValidation)
	}

	return nil
}

func parseInt32(val string) (int32, error) {
	intVal, err := strconv.Atoi(val)
	if err != nil {
		return 0, err
	}

	// Check overflow int32
	if intVal < int(math.MinInt32) || intVal > int(math.MaxInt32) {
		return 0, constants.ErrIntOutOfRange
	}

	//nolint:gosec // ignore integer overflow check for Atoi conversion to int32
	int32Val := int32(intVal)

	return int32Val, nil
}

func parseUint64(val string) (uint64, error) {
	uintVal, err := strconv.ParseUint(val, 10, 64)
	if err != nil {
		return 0, err
	}

	return uintVal, nil
}
