package adminkindboxvalidator

import (
	"context"
	"testing"
	"time"

	"git.gocasts.ir/ebhomengo/niki/entity"
	addressserviceparam "git.gocasts.ir/ebhomengo/niki/param/admin/address"
	agentserviceparam "git.gocasts.ir/ebhomengo/niki/param/admin/agent"
	benefactorserviceparam "git.gocasts.ir/ebhomengo/niki/param/admin/benefactor"
	adminupdateparam "git.gocasts.ir/ebhomengo/niki/param/admin/kind_box"
	refertimeserviceparam "git.gocasts.ir/ebhomengo/niki/param/admin/refer_time"
	"github.com/stretchr/testify/assert"
)

func TestValidator_ValidateUpdateRequest(t *testing.T) {
	now := time.Now()
	twoDaysLater := now.Add(48 * time.Hour)
	threeDaysLater := now.Add(52 * time.Hour)

	mockRepository := NewMockRepository(t)
	mockAgentSvc := NewMockAgentSvc(t)
	mockBenefactorSvc := NewMockBenefactorSvc(t)
	mockReferTimeSvc := NewMockReferTimeSvc(t)
	mockAddressSvc := NewMockAddressSvc(t)

	validator := New(mockRepository, mockAgentSvc, mockBenefactorSvc, mockReferTimeSvc, mockAddressSvc)
	ctx := context.Background()

	t.Run("KindBox Delivered And Benefactor Does Not Exist", func(t *testing.T) {
		req := adminupdateparam.KindBoxUpdateRequest{
			KindBoxID:    1,
			BenefactorID: 1,
		}

		mockRepository.EXPECT().KindBoxExist(ctx, req.KindBoxID).Return(true, nil).Once()

		mockBenefactorSvc.EXPECT().BenefactorExistByID(ctx, benefactorserviceparam.BenefactorExistByIDRequest{ID: req.BenefactorID}).
			Return(benefactorserviceparam.BenefactorExistByIDResponse{Existed: false}, nil).Once()

		mockRepository.EXPECT().GetKindBox(ctx, req.KindBoxID).
			Return(entity.KindBox{Status: entity.KindBoxDeliveredStatus}, nil).Once()

		fieldErrors, err := validator.ValidateUpdateRequest(ctx, req)

		assert.Error(t, err)
		assert.NotNil(t, fieldErrors)
		assert.Contains(t, fieldErrors, "benefactor_id")
	})

	t.Run("KindBox Does Not Exist", func(t *testing.T) {
		req := adminupdateparam.KindBoxUpdateRequest{
			KindBoxID:    1,
			BenefactorID: 1,
		}

		mockRepository.EXPECT().KindBoxExist(ctx, req.KindBoxID).Return(false, nil).Once()

		mockBenefactorSvc.EXPECT().BenefactorExistByID(ctx, benefactorserviceparam.BenefactorExistByIDRequest{ID: req.BenefactorID}).
			Return(benefactorserviceparam.BenefactorExistByIDResponse{Existed: true}, nil).Once()

		mockRepository.EXPECT().GetKindBox(ctx, req.KindBoxID).
			Return(entity.KindBox{}, nil).Once()

		fieldErrors, err := validator.ValidateUpdateRequest(ctx, req)

		assert.Error(t, err)
		assert.NotNil(t, fieldErrors)
		assert.Contains(t, fieldErrors, "KindBoxID")
	})

	t.Run("KindBox Delivered And Does Not Belong to Benefactor", func(t *testing.T) {
		req := adminupdateparam.KindBoxUpdateRequest{
			KindBoxID:    1,
			BenefactorID: 1,
		}

		mockRepository.EXPECT().KindBoxExist(ctx, req.KindBoxID).Return(true, nil).Once()

		mockBenefactorSvc.EXPECT().BenefactorExistByID(ctx, benefactorserviceparam.BenefactorExistByIDRequest{ID: req.BenefactorID}).
			Return(benefactorserviceparam.BenefactorExistByIDResponse{Existed: true}, nil).Once()

		mockRepository.EXPECT().GetKindBox(ctx, req.KindBoxID).
			Return(entity.KindBox{Status: entity.KindBoxDeliveredStatus, BenefactorID: 2}, nil).Once()

		fieldErrors, err := validator.ValidateUpdateRequest(ctx, req)

		assert.Error(t, err)
		assert.NotNil(t, fieldErrors)
		assert.Contains(t, fieldErrors, "benefactor_id")
	})

	t.Run("KindBoxID is Null", func(t *testing.T) {
		req := adminupdateparam.KindBoxUpdateRequest{
			KindBoxID:    0,
			BenefactorID: 1,
		}

		mockBenefactorSvc.EXPECT().BenefactorExistByID(ctx, benefactorserviceparam.BenefactorExistByIDRequest{ID: req.BenefactorID}).
			Return(benefactorserviceparam.BenefactorExistByIDResponse{Existed: true}, nil).Once()

		mockRepository.EXPECT().GetKindBox(ctx, req.KindBoxID).
			Return(entity.KindBox{}, nil).Once()

		fieldErrors, err := validator.ValidateUpdateRequest(ctx, req)

		assert.Error(t, err)
		assert.NotNil(t, fieldErrors)
		assert.Contains(t, fieldErrors, "KindBoxID")
	})

	t.Run("BenefactorID is Null", func(t *testing.T) {
		req := adminupdateparam.KindBoxUpdateRequest{
			KindBoxID:    1,
			BenefactorID: 0,
		}

		mockRepository.EXPECT().KindBoxExist(ctx, req.KindBoxID).Return(true, nil).Once()

		mockRepository.EXPECT().GetKindBox(ctx, req.KindBoxID).
			Return(entity.KindBox{Status: entity.KindBoxDeliveredStatus, BenefactorID: 1}, nil).Once() // Note the different BenefactorID

		fieldErrors, err := validator.ValidateUpdateRequest(ctx, req)

		assert.Error(t, err)
		assert.NotNil(t, fieldErrors)
		assert.Contains(t, fieldErrors, "benefactor_id")
	})

	t.Run("Valid KindBox Delivered Request", func(t *testing.T) {
		req := adminupdateparam.KindBoxUpdateRequest{
			KindBoxID:    1,
			BenefactorID: 1,
		}

		mockRepository.EXPECT().KindBoxExist(ctx, req.KindBoxID).
			Return(true, nil).Once()

		mockBenefactorSvc.EXPECT().BenefactorExistByID(ctx, benefactorserviceparam.BenefactorExistByIDRequest{ID: req.BenefactorID}).
			Return(benefactorserviceparam.BenefactorExistByIDResponse{Existed: true}, nil).Once()

		mockRepository.EXPECT().GetKindBox(ctx, req.KindBoxID).
			Return(entity.KindBox{Status: entity.KindBoxDeliveredStatus}, nil).Once()

		fieldErrors, err := validator.ValidateUpdateRequest(ctx, req)

		assert.Error(t, err)
		assert.NotNil(t, fieldErrors)
	})

	t.Run("KindBox Ready to Return Status and Address Does Not Belong", func(t *testing.T) {
		req := adminupdateparam.KindBoxUpdateRequest{
			KindBoxID:         1,
			BenefactorID:      1,
			ReturnReferDate:   twoDaysLater,
			ReturnReferTimeID: 1,
			ReturnAddressID:   23,
		}

		validReferTime := entity.ReferTime{
			ID:       1,
			Duration: "30m",
			Status:   entity.ReferTimeActiveStatus,
		}

		addressExm := entity.Address{
			ID:           23,
			PostalCode:   "1231231231",
			Address:      "Tehran",
			Name:         "Home",
			CityID:       1,
			ProvinceID:   1,
			BenefactorID: 2,
		}

		mockRepository.EXPECT().KindBoxExist(ctx, req.KindBoxID).
			Return(true, nil).Once()

		mockRepository.EXPECT().GetKindBox(ctx, req.KindBoxID).
			Return(entity.KindBox{Status: entity.KindBoxReadyToReturnStatus, BenefactorID: req.BenefactorID}, nil).Once()

		mockBenefactorSvc.EXPECT().BenefactorExistByID(ctx, benefactorserviceparam.BenefactorExistByIDRequest{ID: req.BenefactorID}).
			Return(benefactorserviceparam.BenefactorExistByIDResponse{Existed: true}, nil).Once()

		mockAddressSvc.EXPECT().GetAddressByID(ctx, addressserviceparam.AddressGetRequest{AddressID: req.ReturnAddressID}).
			Return(addressserviceparam.AddressGetResponse{Address: addressExm}, nil).Once()

		mockReferTimeSvc.EXPECT().GetReferTimeByID(ctx, refertimeserviceparam.GetReferTimeRequest{ReferTimeID: 1}).
			Return(refertimeserviceparam.GetReferTimeResponse{ReferTime: validReferTime}, nil).Once()

		mockRepository.EXPECT().GetKindBox(ctx, uint(req.KindBoxID)).
			Return(entity.KindBox{Status: entity.KindBoxReadyToReturnStatus, BenefactorID: req.BenefactorID}, nil).Once()

		fieldErrors, err := validator.ValidateUpdateRequest(ctx, req)

		assert.Error(t, err)
		assert.NotNil(t, fieldErrors)
		assert.Contains(t, fieldErrors, "return_address_id")
	})

	t.Run("KindBox Ready to Return Status and ReturnReferTimeID Invalid", func(t *testing.T) {
		req := adminupdateparam.KindBoxUpdateRequest{
			KindBoxID:         1,
			BenefactorID:      1,
			ReturnReferDate:   twoDaysLater,
			ReturnReferTimeID: 14,
			ReturnAddressID:   1,
		}

		validAddress := entity.Address{
			ID:           1,
			PostalCode:   "1234567890",
			Address:      "tehran",
			Name:         "home",
			CityID:       1,
			ProvinceID:   1,
			BenefactorID: 1,
		}

		mockRepository.EXPECT().KindBoxExist(ctx, req.KindBoxID).
			Return(true, nil).Once()

		mockBenefactorSvc.EXPECT().BenefactorExistByID(ctx, benefactorserviceparam.BenefactorExistByIDRequest{ID: req.BenefactorID}).
			Return(benefactorserviceparam.BenefactorExistByIDResponse{Existed: true}, nil).Once()

		mockRepository.EXPECT().GetKindBox(ctx, req.KindBoxID).
			Return(entity.KindBox{Status: entity.KindBoxReadyToReturnStatus, BenefactorID: req.BenefactorID}, nil).Once()

		mockAddressSvc.EXPECT().GetAddressByID(ctx, addressserviceparam.AddressGetRequest{AddressID: req.ReturnAddressID}).
			Return(addressserviceparam.AddressGetResponse{Address: validAddress}, nil).Once()

		mockReferTimeSvc.EXPECT().GetReferTimeByID(ctx, refertimeserviceparam.GetReferTimeRequest{ReferTimeID: req.ReturnReferTimeID}).
			Return(refertimeserviceparam.GetReferTimeResponse{}, nil).Once()

		mockRepository.EXPECT().GetKindBox(ctx, req.KindBoxID).
			Return(entity.KindBox{Status: entity.KindBoxReadyToReturnStatus, BenefactorID: req.BenefactorID}, nil).Once()

		fieldErrors, err := validator.ValidateUpdateRequest(ctx, req)

		assert.Error(t, err)
		assert.NotNil(t, fieldErrors)
		assert.Contains(t, fieldErrors, "return_refere_time_id")
	})

	t.Run("Valid KindBox Ready to Return Status Request", func(t *testing.T) {
		req := adminupdateparam.KindBoxUpdateRequest{
			KindBoxID:         1,
			BenefactorID:      1,
			ReturnReferDate:   twoDaysLater,
			ReturnReferTimeID: 1,
			ReturnAddressID:   1,
		}

		validAddress := entity.Address{
			ID:           1,
			PostalCode:   "1234567890",
			Address:      "tehran",
			Name:         "home",
			CityID:       1,
			ProvinceID:   1,
			BenefactorID: 1,
		}

		validReferTime := entity.ReferTime{
			ID:       1,
			Duration: "30m",
			Status:   entity.ReferTimeActiveStatus,
		}

		mockRepository.EXPECT().KindBoxExist(ctx, req.KindBoxID).
			Return(true, nil).Once()

		mockBenefactorSvc.EXPECT().BenefactorExistByID(ctx, benefactorserviceparam.BenefactorExistByIDRequest{ID: req.BenefactorID}).
			Return(benefactorserviceparam.BenefactorExistByIDResponse{Existed: true}, nil).Once()

		mockRepository.EXPECT().GetKindBox(ctx, req.KindBoxID).
			Return(entity.KindBox{Status: entity.KindBoxReadyToReturnStatus, BenefactorID: req.BenefactorID}, nil).Once()

		mockAddressSvc.EXPECT().GetAddressByID(ctx, addressserviceparam.AddressGetRequest{AddressID: req.ReturnAddressID}).
			Return(addressserviceparam.AddressGetResponse{Address: validAddress}, nil).Once()

		mockReferTimeSvc.EXPECT().GetReferTimeByID(ctx, refertimeserviceparam.GetReferTimeRequest{ReferTimeID: req.ReturnReferTimeID}).
			Return(refertimeserviceparam.GetReferTimeResponse{ReferTime: validReferTime}, nil).Once()

		mockRepository.EXPECT().GetKindBox(ctx, uint(1)).
			Return(entity.KindBox{Status: entity.KindBoxReadyToReturnStatus, BenefactorID: req.BenefactorID}, nil).Once()

		fieldErrors, err := validator.ValidateUpdateRequest(ctx, req)

		assert.NoError(t, err)
		assert.Nil(t, fieldErrors)
	})

	t.Run("Assigned Receiver Agent Status And ReturnReferDate Not Null", func(t *testing.T) {
		req := adminupdateparam.KindBoxUpdateRequest{
			KindBoxID:       1,
			BenefactorID:    1,
			ReturnReferDate: threeDaysLater,
		}

		mockRepository.EXPECT().KindBoxExist(ctx, req.KindBoxID).Return(true, nil).Once()
		mockBenefactorSvc.EXPECT().BenefactorExistByID(ctx, benefactorserviceparam.BenefactorExistByIDRequest{ID: req.BenefactorID}).
			Return(benefactorserviceparam.BenefactorExistByIDResponse{Existed: true}, nil).Once()
		mockRepository.EXPECT().GetKindBox(ctx, req.KindBoxID).Return(entity.KindBox{Status: entity.KindBoxAssignedReceiverAgentStatus, BenefactorID: req.BenefactorID}, nil).Twice()

		fieldErrors, err := validator.ValidateUpdateRequest(ctx, req)

		assert.Error(t, err)
		assert.NotNil(t, fieldErrors)
		assert.Contains(t, fieldErrors, "Return Refer Date")
	})

	t.Run("Assigned Receiver Agent Status And ReturnAddressID Not Null", func(t *testing.T) {
		req := adminupdateparam.KindBoxUpdateRequest{
			KindBoxID:       1,
			BenefactorID:    1,
			ReturnAddressID: 1,
		}

		mockRepository.EXPECT().KindBoxExist(ctx, req.KindBoxID).Return(true, nil).Once()
		mockBenefactorSvc.EXPECT().BenefactorExistByID(ctx, benefactorserviceparam.BenefactorExistByIDRequest{ID: req.BenefactorID}).
			Return(benefactorserviceparam.BenefactorExistByIDResponse{Existed: true}, nil).Once()
		mockRepository.EXPECT().GetKindBox(ctx, req.KindBoxID).Return(entity.KindBox{Status: entity.KindBoxAssignedReceiverAgentStatus, BenefactorID: req.BenefactorID}, nil).Twice()

		fieldErrors, err := validator.ValidateUpdateRequest(ctx, req)

		assert.Error(t, err)
		assert.NotNil(t, fieldErrors)
		assert.Contains(t, fieldErrors, "Return Address ID")
	})

	t.Run("Valid KindBox Assigned Receiver Agent Status Request", func(t *testing.T) {
		req := adminupdateparam.KindBoxUpdateRequest{
			KindBoxID:       1,
			BenefactorID:    1,
			ReceiverAgentID: 1,
		}

		mockRepository.EXPECT().KindBoxExist(ctx, req.KindBoxID).Return(true, nil).Once()

		mockBenefactorSvc.EXPECT().BenefactorExistByID(ctx, benefactorserviceparam.BenefactorExistByIDRequest{ID: req.BenefactorID}).
			Return(benefactorserviceparam.BenefactorExistByIDResponse{Existed: true}, nil).Once()

		mockRepository.EXPECT().GetKindBox(ctx, req.KindBoxID).Return(entity.KindBox{
			Status:       entity.KindBoxAssignedReceiverAgentStatus,
			BenefactorID: 1,
		}, nil).Once()

		mockAgentSvc.EXPECT().AgentExistByID(ctx, agentserviceparam.AdminAgentExistByIDRequest{AgentID: 1}).Return(agentserviceparam.AdminAgentExistByIDResponse{Exist: true}, nil).Once()

		mockRepository.EXPECT().GetKindBox(ctx, req.KindBoxID).Return(entity.KindBox{
			Status:       entity.KindBoxAssignedReceiverAgentStatus,
			BenefactorID: req.BenefactorID,
		}, nil).Once()

		fieldErrors, err := validator.ValidateUpdateRequest(ctx, req)

		assert.NoError(t, err)
		assert.Nil(t, fieldErrors)
	})

	t.Run("Valid Kindbox Returned Status Request", func(t *testing.T) {
		req := adminupdateparam.KindBoxUpdateRequest{
			KindBoxID:    1,
			BenefactorID: 1,
		}

		mockRepository.EXPECT().KindBoxExist(ctx, req.KindBoxID).Return(true, nil).Once()
		mockBenefactorSvc.EXPECT().BenefactorExistByID(ctx, benefactorserviceparam.BenefactorExistByIDRequest{ID: req.BenefactorID}).
			Return(benefactorserviceparam.BenefactorExistByIDResponse{Existed: true}, nil).Once()
		mockRepository.EXPECT().GetKindBox(ctx, req.KindBoxID).Return(entity.KindBox{Status: entity.KindBoxReturnedStatus}, nil).Once()

		fieldErrors, err := validator.ValidateUpdateRequest(ctx, req)

		assert.Error(t, err)
		assert.NotNil(t, fieldErrors)
	})

	t.Run("Invalid KindBox Enumerate Status Amount", func(t *testing.T) {
		req := adminupdateparam.KindBoxUpdateRequest{
			KindBoxID:    1,
			BenefactorID: 1,
			Amount:       0,
		}

		mockRepository.EXPECT().KindBoxExist(ctx, req.KindBoxID).Return(true, nil).Once()

		mockBenefactorSvc.EXPECT().BenefactorExistByID(ctx, benefactorserviceparam.BenefactorExistByIDRequest{ID: req.BenefactorID}).
			Return(benefactorserviceparam.BenefactorExistByIDResponse{Existed: true}, nil).Once()

		mockRepository.EXPECT().GetKindBox(ctx, req.KindBoxID).
			Return(entity.KindBox{
				Status:       entity.KindBoxEnumeratedStatus,
				BenefactorID: req.BenefactorID,
			}, nil).Twice()

		fieldErrors, err := validator.ValidateUpdateRequest(ctx, req)

		assert.Error(t, err)
		assert.NotNil(t, fieldErrors)
		assert.Contains(t, fieldErrors, "amount")
	})

	t.Run("Valid KindBox Enumerated Status Request", func(t *testing.T) {
		req := adminupdateparam.KindBoxUpdateRequest{
			KindBoxID:    1,
			BenefactorID: 1,
			Amount:       100,
		}

		mockRepository.EXPECT().KindBoxExist(ctx, req.KindBoxID).Return(true, nil).Once()

		mockBenefactorSvc.EXPECT().BenefactorExistByID(ctx, benefactorserviceparam.BenefactorExistByIDRequest{ID: req.BenefactorID}).
			Return(benefactorserviceparam.BenefactorExistByIDResponse{Existed: true}, nil).Once()

		mockRepository.EXPECT().GetKindBox(ctx, req.KindBoxID).
			Return(entity.KindBox{
				Status:       entity.KindBoxEnumeratedStatus,
				BenefactorID: 1,
			}, nil).Twice()

		fieldErrors, err := validator.ValidateUpdateRequest(ctx, req)

		assert.NoError(t, err)
		assert.Nil(t, fieldErrors)
	})
}
