package adminkindboxvalidator

import (
	"context"
	"errors"
	"fmt"
	"slices"
	"time"

	entity "git.gocasts.ir/ebhomengo/niki/entity"
	params "git.gocasts.ir/ebhomengo/niki/param"
	adminaddressparam "git.gocasts.ir/ebhomengo/niki/param/admin/address"
	agentparam "git.gocasts.ir/ebhomengo/niki/param/admin/agent"
	param "git.gocasts.ir/ebhomengo/niki/param/admin/benefactor"
	refertimeparam "git.gocasts.ir/ebhomengo/niki/param/admin/refer_time"
	errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
	richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
	validation "github.com/go-ozzo/ozzo-validation/v4"
)

const (
	MaxLengthQuerySearch = 32
)

//go:generate mockery --name Repository
type Repository interface {
	KindBoxExist(ctx context.Context, kindBoxID uint) (bool, error)
	GetKindBox(ctx context.Context, kindBoxID uint) (entity.KindBox, error)
}

//go:generate mockery --name AgentSvc
type AgentSvc interface {
	AgentExistByID(ctx context.Context, req agentparam.AdminAgentExistByIDRequest) (agentparam.AdminAgentExistByIDResponse, error)
}

//go:generate mockery --name AddressSvc
type AddressSvc interface {
	GetAddressByID(ctx context.Context, request adminaddressparam.AddressGetRequest) (adminaddressparam.AddressGetResponse, error)
}

//go:generate mockery --name ReferTimeSvc
type ReferTimeSvc interface {
	GetReferTimeByID(ctx context.Context, req refertimeparam.GetReferTimeRequest) (refertimeparam.GetReferTimeResponse, error)
}

//go:generate mockery --name BenefactorSvc
type BenefactorSvc interface {
	BenefactorExistByID(ctx context.Context, request param.BenefactorExistByIDRequest) (param.BenefactorExistByIDResponse, error)
}

type Validator struct {
	repo          Repository
	agentSvc      AgentSvc
	benefactorSvc BenefactorSvc
	referTimeSvc  ReferTimeSvc
	addressSvc    AddressSvc
}

func New(repo Repository, agentSvc AgentSvc, benefactorSvc BenefactorSvc, referTimeSvc ReferTimeSvc, addressSvc AddressSvc) Validator {
	return Validator{repo: repo, agentSvc: agentSvc, benefactorSvc: benefactorSvc, referTimeSvc: referTimeSvc, addressSvc: addressSvc}
}

func (v Validator) doesKindBoxExist(ctx context.Context) validation.RuleFunc {
	return func(value interface{}) error {
		const op = "Validator.doesKindBoxExist"
		kindBoxID, ok := value.(uint)
		if !ok {
			return richerror.New(op).WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindInvalid)
		}
		if kindBoxID == 0 {
			return richerror.New(op).WithMessage(errmsg.ErrorMsgInvalidInput).WithKind(richerror.KindInvalid)
		}
		exists, err := v.repo.KindBoxExist(ctx, kindBoxID)
		if err != nil {
			return richerror.New(op).WithErr(err).WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindUnexpected)
		}
		if !exists {
			return richerror.New(op).WithMessage(errmsg.ErrorMsgNotFound).WithKind(richerror.KindNotFound)
		}
		return nil
	}
}

func (v Validator) doesBenefactorExist(ctx context.Context) validation.RuleFunc {
	return func(value interface{}) error {
		const op = "Validator.doesBenefactorExist"

		benefactorID, ok := value.(uint)
		if !ok {
			return richerror.New(op).
				WithMessage("invalid type for benefactorID").
				WithKind(richerror.KindInvalid)
		}

		_, err := v.benefactorSvc.BenefactorExistByID(ctx, param.BenefactorExistByIDRequest{ID: benefactorID})
		if err != nil {
			return richerror.New(op).
				WithErr(err).
				WithMessage("could not check if benefactor exists").
				WithKind(richerror.KindUnexpected)
		}

		return nil
	}
}

func (v Validator) doesBenefactorHaveKindBox(ctx context.Context, kindBoxID uint, benefactorID uint) validation.RuleFunc {
	return func(value interface{}) error {
		const op = "Validator.doesBenefactorHaveKindBox"

		fetchedKindBox, err := v.repo.GetKindBox(ctx, kindBoxID)
		if err != nil {
			return richerror.New(op).WithErr(err).WithMessage("could not fetch KindBox").WithKind(richerror.KindUnexpected)
		}

		if fetchedKindBox.BenefactorID != benefactorID {
			return richerror.New(op).WithMessage("benefactor does not have the specified kind box").WithKind(richerror.KindInvalid)
		}
		return nil
	}
}

func (v Validator) checkKindBoxStatusForAssigningReceiverAgent(ctx context.Context) validation.RuleFunc {
	return func(value interface{}) error {
		const op = "Validator.checkKindBoxStatusForAssigningReceiverAgent"
		kindboxID, ok := value.(uint)
		if !ok {
			return richerror.New(op).WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindInvalid)
		}
		kindBox, err := v.repo.GetKindBox(ctx, kindboxID)
		if err != nil {
			return richerror.New(op).WithErr(err).WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindUnexpected)
		}
		if kindBox.Status != entity.KindBoxReadyToReturnStatus {
			return richerror.New(op).WithMessage(errmsg.ErrorMsgAssignReceiverAgentKindBoxStatus).WithKind(richerror.KindInvalid)
		}

		return nil
	}
}

func (v Validator) doesAgentExist(ctx context.Context) validation.RuleFunc {
	return func(value interface{}) error {
		const op = "Validator.doesAgentExist"
		agentID, ok := value.(uint)
		if !ok {
			return richerror.New(op).WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindInvalid)
		}
		resp, err := v.agentSvc.AgentExistByID(ctx, agentparam.AdminAgentExistByIDRequest{AgentID: agentID})
		if err != nil {
			return richerror.New(op).WithErr(err).WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindUnexpected)
		}
		if !resp.Exist {
			return richerror.New(op).WithMessage(errmsg.ErrorMsgNotFound).WithKind(richerror.KindNotFound)
		}
		return nil
	}
}

func (v Validator) isReturnReferTimeIDValid(ctx context.Context) validation.RuleFunc {
	return func(value interface{}) error {
		const op = "Validator.isReturnTimeIDValid"
		referTimeID, ok := value.(uint)
		if !ok {
			return richerror.New(op).WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindInvalid)
		}
		resp, err := v.referTimeSvc.GetReferTimeByID(ctx, refertimeparam.GetReferTimeRequest{
			ReferTimeID: referTimeID,
		})
		if err != nil {
			return richerror.New(op).WithErr(err).WithMessage(errmsg.ErrorMsgReferTimeNotFound).WithKind(richerror.KindNotFound)
		}
		if resp.Data.Status != entity.ReferTimeActiveStatus {
			return richerror.New(op).WithMessage(errmsg.ErrorMsgReferTimeIsNotActive).WithKind(richerror.KindInvalid)
		}
		return nil
	}
}

func (v Validator) CheckKindBoxStatusForEnumeration(ctx context.Context) validation.RuleFunc {
	return func(value interface{}) error {
		kindBoxID, ok := value.(uint)
		if !ok {
			return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
		}
		kindBox, err := v.repo.GetKindBox(ctx, kindBoxID)
		if err != nil {
			return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
		}
		if kindBox.Status != entity.KindBoxReturnedStatus {
			return fmt.Errorf(errmsg.ErrorMsgReturnKindBoxStatus)
		}

		return nil
	}
}

func (v Validator) doesBenefactorAddressExist(ctx context.Context, kindBoxID uint, benefactorID uint, addressID uint) validation.RuleFunc {
	return func(value interface{}) error {
		const op = "Validator.doesBenefactorAddressExist"
		addressRequest := adminaddressparam.AddressGetRequest{
			AddressID: addressID,
		}
		addressResponse, err := v.addressSvc.GetAddressByID(ctx, addressRequest)
		if err != nil {
			return richerror.New(op).WithErr(err).WithMessage("failed to retrieve address").WithKind(richerror.KindUnexpected)
		}

		if addressResponse.Data.BenefactorID != benefactorID {
			return richerror.New(op).WithMessage("the specified address does not belong to the benefactor").WithKind(richerror.KindInvalid)
		}
		return nil
	}
}

func (v Validator) isDateValid(value interface{}) error {
	const op = "Validator.isDateValid"
	date, ok := value.(time.Time)
	if !ok {
		return richerror.New(op).WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindInvalid)
	}
	if date.Before(time.Now()) {
		return richerror.New(op).WithMessage(errmsg.ErrorMsgInvalidInput).WithKind(richerror.KindInvalid)
	}
	return nil
}

func (v Validator) isAmountValid(ctx context.Context) validation.RuleFunc {
	return func(value interface{}) error {
		const op = "Validator.isAmountValid"
		amount, ok := value.(uint)
		if !ok || amount == 0 {
			return richerror.New(op).WithMessage("amount is required").WithKind(richerror.KindInvalid)
		}
		return nil
	}
}

// processing errors
func (v Validator) processValidationErrors(err error, fieldErrors map[string]string) {
	var errV validation.Errors
	if errors.As(err, &errV) {
		for key, value := range errV {
			if value != nil {
				fieldErrors[key] = value.Error()
			}
		}
	}
}

func (v Validator) AreSortFieldsValid(validSortFields []string) validation.RuleFunc {
	return func(value interface{}) error {
		sort, ok := value.(params.SortRequest)
		if !ok {
			return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
		}
		if sort.Field == "" && sort.Direction != "" {
			return fmt.Errorf(errmsg.ErrorMsgSortFieldIsRequired)
		}
		if sort.Direction != "" && sort.Direction != params.AscSortDirection && sort.Direction != params.DescSortDirection {
			return fmt.Errorf(errmsg.ErrorMsgSortDirectionShouldBeAscOrDesc)
		}
		if sort.Field != "" && !slices.Contains(validSortFields, sort.Field) {
			return fmt.Errorf(errmsg.ErrorMsgSortFieldIsNotValid)
		}

		return nil
	}
}

func (v Validator) AreFilterFieldsValid(validFilters []string) validation.RuleFunc {
	return func(value interface{}) error {
		filters, ok := value.(params.FilterRequest)
		if !ok {
			return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
		}
		for filter := range filters {
			if !slices.Contains(validFilters, filter) {
				return fmt.Errorf(errmsg.ErrorMsgFiltersAreNotValid)
			}
		}

		return nil
	}
}

func (v Validator) AreSearchValid() validation.RuleFunc {
	return func(value interface{}) error {
		search, ok := value.(params.SearchRequest)
		if !ok {
			return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
		}
		if len(search.Query) > MaxLengthQuerySearch {
			return fmt.Errorf(errmsg.ErrorMsgInvalidInput)
		}

		return nil
	}
}
