package pickup_point

import (
	"context"
	"database/sql/driver"
	"fmt"
	"github.com/DATA-DOG/go-sqlmock"
	"github.com/UpmeshLTD/urus-aio/infrastructure/common"
	"github.com/UpmeshLTD/urus-aio/infrastructure/db/entities"
	"github.com/UpmeshLTD/urus-aio/shipment/db/gormdb"
	"github.com/UpmeshLTD/urus-aio/shipment/server/http/dto"
	"github.com/jaswdr/faker"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
	"gorm.io/gorm"
	"regexp"
	"strings"
	"testing"
	"time"

	tt "github.com/UpmeshLTD/urus-aio/infrastructure/testing"
)

func TestPickupPointRepository(t *testing.T) {
	suite.Run(t, new(RepositoryTestSuite))
}

func NewSample() Sample {
	fake := faker.New()
	person := fake.Person()
	return Sample{
		PickupPointID: fake.Int64(),
		CountryID:     fake.Int64(),
		AddressID:     fake.Int64(),
		WorkspaceID:   fake.UUID().V4(),
		FirstName:     person.FirstName(),
		LastName:      person.LastName(),
		PhoneNumber:   person.Contact().Phone,
		Email:         person.Contact().Email,
		Default:       fake.Bool(),
		Deactivate:    fake.Bool(),
	}
}

func NewSamplePickupPoint() entities.PickupPoint {
	fake := faker.New()
	address := fake.Address()
	sample := NewSample()
	location1 := entities.Location{
		BaseEntity:        BaseEntityFromInt64(fake.Int64()),
		CountryID:         sample.CountryID,
		ParentID:          0,
		InternationalName: fake.Lorem().Word(),
		LocalizedName:     fake.Lorem().Word(),
	}
	location2 := entities.Location{
		BaseEntity:        BaseEntityFromInt64(fake.Int64()),
		CountryID:         sample.CountryID,
		ParentID:          location1.ID.ID,
		InternationalName: fake.Lorem().Word(),
		LocalizedName:     fake.Lorem().Word(),
	}
	location3 := entities.Location{
		BaseEntity:        BaseEntityFromInt64(fake.Int64()),
		CountryID:         sample.CountryID,
		ParentID:          location2.ID.ID,
		InternationalName: fake.Lorem().Word(),
		LocalizedName:     fake.Lorem().Word(),
	}
	return entities.PickupPoint{
		BaseEntity:  BaseEntityFromInt64(sample.PickupPointID),
		WorkspaceID: sample.WorkspaceID,
		AddressID:   sample.AddressID,
		FirstName:   sample.FirstName,
		LastName:    sample.LastName,
		PhoneNumber: sample.PhoneNumber,
		Email:       sample.Email,
		Default:     sample.Default,
		Deactivate:  sample.Deactivate,
		Address: entities.Address{
			BaseEntity:        BaseEntityFromInt64(sample.AddressID),
			CountryID:         sample.CountryID,
			Address1:          address.Address(),
			Address2:          address.SecondaryAddress(),
			PostalCode:        address.PostCode(),
			UnitNumber:        address.BuildingNumber(),
			Deactivate:        fake.Bool(),
			SubLevelLocations: []entities.Location{location1, location2, location3},
		},
	}
}

func BaseEntityFromInt64(val int64) entities.BaseEntity {
	return entities.BaseEntity{
		ID: entities.IDFromInt64(val),
		AuditTime: entities.AuditTime{
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		},
	}
}

type Sample struct {
	PickupPointID int64
	AddressID     int64
	CountryID     int64
	WorkspaceID   string
	FirstName     string
	LastName      string
	PhoneNumber   string
	Email         string
	Default       bool
	Deactivate    bool
}

type RepositoryTestSuite struct {
	suite.Suite
	db     *gorm.DB
	gormDB gormdb.GormDB
	ctx    context.Context
	mock   sqlmock.Sqlmock
	repo   Repository
}

func (s *RepositoryTestSuite) SetupSuite() {
	var err error
	s.db, s.mock, err = tt.MockGormSqlmock()
	require.NoError(s.T(), err)

	s.ctx = context.TODO()
	s.gormDB = gormdb.Wrap(s.db)
	s.repo = NewRepository(s.gormDB)
}

func (s *RepositoryTestSuite) AfterTest(_, _ string) {
	require.NoError(s.T(), s.mock.ExpectationsWereMet())
}

func (s *RepositoryTestSuite) Test_GetPickupPointByID_Success() {
	sample := NewSamplePickupPoint()
	expectedResult := &sample

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "pickup_points" WHERE workspace_id = $1 AND id = $2 AND "pickup_points"."deleted_at" IS NULL ORDER BY "pickup_points"."id" LIMIT 1`)).
		WithArgs(sample.WorkspaceID, sample.ID.ID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "workspace_id", "address_id", "first_name", "last_name", "phone_number", "email", "default", "deactivate", "created_at", "updated_at"}).
			AddRow(
				sample.ID.ID,
				sample.WorkspaceID,
				sample.AddressID,
				sample.FirstName,
				sample.LastName,
				sample.PhoneNumber,
				sample.Email,
				sample.Default,
				sample.Deactivate,
				sample.CreatedAt,
				sample.UpdatedAt,
			))

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "addresses" WHERE "addresses"."id" = $1 AND "addresses"."deleted_at" IS NULL`)).
		WithArgs(sample.Address.ID.ID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "address_1", "address_2", "postal_code", "country_id", "deactivate", "unit_number", "created_at", "updated_at"}).
			AddRow(
				sample.Address.ID.ID,
				sample.Address.Address1,
				sample.Address.Address2,
				sample.Address.PostalCode,
				sample.Address.CountryID,
				sample.Address.Deactivate,
				sample.Address.UnitNumber,
				sample.Address.CreatedAt,
				sample.Address.UpdatedAt,
			))

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "address_sub_levels" WHERE "address_sub_levels"."address_id" = $1`)).
		WithArgs(sample.Address.ID.ID).
		WillReturnRows(func() (rows *sqlmock.Rows) {
			rows = sqlmock.NewRows([]string{"address_id", "location_id"})
			for _, v := range sample.Address.SubLevelLocations {
				rows.AddRow(sample.Address.ID.ID, v.ID.ID)
			}
			return rows
		}())

	s.mock.ExpectQuery(regexp.QuoteMeta(func() string {
		var arr []string
		for i := 1; i <= len(sample.Address.SubLevelLocations); i++ {
			arr = append(arr, fmt.Sprintf("$%d", i))
		}
		return fmt.Sprintf(`SELECT * FROM "locations" WHERE "locations"."id" IN (%s) AND "locations"."deleted_at" IS NULL`, strings.Join(arr, ","))
	}())).
		WithArgs(func() (ids []driver.Value) {
			for _, v := range sample.Address.SubLevelLocations {
				ids = append(ids, v.ID.ID)
			}
			return ids
		}()...).
		WillReturnRows(func() (rows *sqlmock.Rows) {
			rows = sqlmock.NewRows([]string{"id", "localized_name", "international_name", "country_id", "parent_id", "created_at", "updated_at"})
			for _, v := range sample.Address.SubLevelLocations {
				rows.AddRow(
					v.ID.ID,
					v.LocalizedName,
					v.InternationalName,
					v.CountryID,
					v.ParentID,
					v.CreatedAt,
					v.UpdatedAt,
				)
			}
			return rows
		}())

	result, err := s.repo.GetPickupPointByID(s.ctx, sample.WorkspaceID, sample.ID)
	require.NoError(s.T(), err)
	assert.Equal(s.T(), expectedResult, result, "they should be equal")
}

func (s *RepositoryTestSuite) Test_GetPickupPointByID_Failed() {
	sample := NewSamplePickupPoint()
	expectedError := gorm.ErrRecordNotFound

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "pickup_points" WHERE workspace_id = $1 AND id = $2 AND "pickup_points"."deleted_at" IS NULL ORDER BY "pickup_points"."id" LIMIT 1`)).
		WithArgs(sample.WorkspaceID, sample.ID.ID).
		WillReturnError(expectedError)

	result, err := s.repo.GetPickupPointByID(s.ctx, sample.WorkspaceID, sample.ID)
	require.Error(s.T(), err)
	assert.Nil(s.T(), result, "result should be nil")
	assert.Equal(s.T(), expectedError, err, "they should be equal")
}

func (s *RepositoryTestSuite) Test_GetListOfPickupPoints_Success() {
	sample := NewSamplePickupPoint()
	args := &dto.GetPickupPointsRequest{
		Deactivate: false,
		Pagination: common.Pagination{
			Limit: 10,
		},
	}
	expectedResult := []*entities.PickupPoint{&sample}
	expectedTotal := int64(len(expectedResult))

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "pickup_points" WHERE workspace_id = $1 AND deactivate = false AND "pickup_points"."deleted_at" IS NULL`)).
		WithArgs(sample.WorkspaceID).
		WillReturnRows(sqlmock.NewRows([]string{"count(*)"}).AddRow(expectedTotal))

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "pickup_points" WHERE workspace_id = $1 AND deactivate = false AND "pickup_points"."deleted_at" IS NULL ORDER BY id desc LIMIT 10`)).
		WithArgs(sample.WorkspaceID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "workspace_id", "address_id", "first_name", "last_name", "phone_number", "email", "default", "deactivate", "created_at", "updated_at"}).
			AddRow(
				sample.ID.ID,
				sample.WorkspaceID,
				sample.AddressID,
				sample.FirstName,
				sample.LastName,
				sample.PhoneNumber,
				sample.Email,
				sample.Default,
				sample.Deactivate,
				sample.CreatedAt,
				sample.UpdatedAt,
			))

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "addresses" WHERE "addresses"."id" = $1 AND "addresses"."deleted_at" IS NULL`)).
		WithArgs(sample.Address.ID.ID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "address_1", "address_2", "postal_code", "country_id", "deactivate", "unit_number", "created_at", "updated_at"}).
			AddRow(
				sample.Address.ID.ID,
				sample.Address.Address1,
				sample.Address.Address2,
				sample.Address.PostalCode,
				sample.Address.CountryID,
				sample.Address.Deactivate,
				sample.Address.UnitNumber,
				sample.Address.CreatedAt,
				sample.Address.UpdatedAt,
			))

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "address_sub_levels" WHERE "address_sub_levels"."address_id" = $1`)).
		WithArgs(sample.Address.ID.ID).
		WillReturnRows(func() (rows *sqlmock.Rows) {
			rows = sqlmock.NewRows([]string{"address_id", "location_id"})
			for _, v := range sample.Address.SubLevelLocations {
				rows.AddRow(sample.Address.ID.ID, v.ID.ID)
			}
			return rows
		}())

	s.mock.ExpectQuery(regexp.QuoteMeta(func() string {
		var arr []string
		for i := 1; i <= len(sample.Address.SubLevelLocations); i++ {
			arr = append(arr, fmt.Sprintf("$%d", i))
		}
		return fmt.Sprintf(`SELECT * FROM "locations" WHERE "locations"."id" IN (%s) AND "locations"."deleted_at" IS NULL`, strings.Join(arr, ","))
	}())).
		WithArgs(func() (ids []driver.Value) {
			for _, v := range sample.Address.SubLevelLocations {
				ids = append(ids, v.ID.ID)
			}
			return ids
		}()...).
		WillReturnRows(func() (rows *sqlmock.Rows) {
			rows = sqlmock.NewRows([]string{"id", "localized_name", "international_name", "country_id", "parent_id", "created_at", "updated_at"})
			for _, v := range sample.Address.SubLevelLocations {
				rows.AddRow(
					v.ID.ID,
					v.LocalizedName,
					v.InternationalName,
					v.CountryID,
					v.ParentID,
					v.CreatedAt,
					v.UpdatedAt,
				)
			}
			return rows
		}())

	result, total, err := s.repo.GetListOfPickupPoints(s.ctx, sample.WorkspaceID, args)
	require.NoError(s.T(), err)
	assert.Equal(s.T(), expectedTotal, total, "they should be equal")
	assert.Equal(s.T(), expectedResult, result, "they should be equal")
}

func (s *RepositoryTestSuite) Test_GetListOfPickupPoints_Success_Empty() {
	sample := NewSamplePickupPoint()
	args := &dto.GetPickupPointsRequest{
		Deactivate: false,
		Pagination: common.Pagination{
			Limit: 10,
		},
	}
	expectedResult := make([]*entities.PickupPoint, 0)
	expectedTotal := int64(0)

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "pickup_points" WHERE workspace_id = $1 AND deactivate = false AND "pickup_points"."deleted_at" IS NULL`)).
		WithArgs(sample.WorkspaceID).
		WillReturnRows(sqlmock.NewRows([]string{"count(*)"}).AddRow(expectedTotal))

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "pickup_points" WHERE workspace_id = $1 AND deactivate = false AND "pickup_points"."deleted_at" IS NULL ORDER BY id desc LIMIT 10`)).
		WithArgs(sample.WorkspaceID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "workspace_id", "address_id", "first_name", "last_name", "phone_number", "email", "default", "deactivate", "created_at", "updated_at"}))

	result, total, err := s.repo.GetListOfPickupPoints(s.ctx, sample.WorkspaceID, args)
	require.NoError(s.T(), err)
	assert.Equal(s.T(), expectedTotal, total, "they should be equal")
	assert.Equal(s.T(), expectedResult, result, "they should be equal")
}

func (s *RepositoryTestSuite) Test_GetListOfPickupPoints_Failed() {
	sample := NewSamplePickupPoint()
	expectedTotal := int64(0)
	expectedError := gorm.ErrInvalidData

	s.mock.ExpectQuery(`SELECT`).WillReturnError(expectedError)

	result, total, err := s.repo.GetListOfPickupPoints(s.ctx, sample.WorkspaceID, &dto.GetPickupPointsRequest{})
	require.Error(s.T(), err)
	assert.Nil(s.T(), result, "result should be nil")
	assert.Equal(s.T(), expectedTotal, total, "they should be equal")
	assert.Equal(s.T(), expectedError, err, "they should be equal")
}

func (s *RepositoryTestSuite) Test_CreatePickupPoint_Success() {
	sample := NewSamplePickupPoint()
	expectedResult := &entities.PickupPoint{
		WorkspaceID: sample.WorkspaceID,
		AddressID:   sample.AddressID,
		FirstName:   sample.FirstName,
		LastName:    sample.LastName,
		PhoneNumber: sample.PhoneNumber,
	}

	s.mock.ExpectBegin()
	s.mock.ExpectQuery(`INSERT INTO "pickup_points"`).WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(sample.ID.ID))
	s.mock.ExpectCommit()

	result, err := s.repo.CreatePickupPoint(s.ctx, expectedResult)
	require.NoError(s.T(), err)
	assert.Equal(s.T(), expectedResult.ID, sample.ID, "they should be equal")
	assert.Equal(s.T(), expectedResult, result, "they should be equal")
}

func (s *RepositoryTestSuite) Test_CreatePickupPoint_Success_SetDefault() {
	sample := NewSamplePickupPoint()
	expectedResult := &entities.PickupPoint{
		BaseEntity:  sample.BaseEntity,
		WorkspaceID: sample.WorkspaceID,
		AddressID:   sample.AddressID,
		FirstName:   sample.FirstName,
		LastName:    sample.LastName,
		PhoneNumber: sample.PhoneNumber,
		Default:     true,
	}

	s.mock.ExpectBegin()
	s.mock.ExpectQuery(`INSERT INTO "pickup_points"`).WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(sample.ID.ID))
	s.mock.ExpectExec(regexp.QuoteMeta(`UPDATE "pickup_points" SET "default"=$1,"updated_at"=$2 WHERE (workspace_id = $3 AND id <> $4) AND "pickup_points"."deleted_at" IS NULL`)).
		WithArgs(false, sqlmock.AnyArg(), expectedResult.WorkspaceID, sample.ID.ID).
		WillReturnResult(driver.ResultNoRows)
	s.mock.ExpectCommit()

	result, err := s.repo.CreatePickupPoint(s.ctx, expectedResult)
	require.NoError(s.T(), err)
	assert.Equal(s.T(), expectedResult.ID, sample.ID, "they should be equal")
	assert.Equal(s.T(), expectedResult, result, "they should be equal")
}

func (s *RepositoryTestSuite) Test_CreatePickupPoint_Failed_Create() {
	sample := NewSamplePickupPoint()
	expectedError := gorm.ErrInvalidData

	s.mock.ExpectBegin()
	s.mock.ExpectQuery(`INSERT INTO "pickup_points"`).WillReturnError(expectedError)
	s.mock.ExpectRollback()

	result, err := s.repo.CreatePickupPoint(s.ctx, &entities.PickupPoint{
		BaseEntity:  sample.BaseEntity,
		WorkspaceID: sample.WorkspaceID,
		AddressID:   sample.AddressID,
		FirstName:   sample.FirstName,
		LastName:    sample.LastName,
		PhoneNumber: sample.PhoneNumber,
	})
	require.Error(s.T(), err)
	assert.Nil(s.T(), result, "result should be nil")
	assert.Equal(s.T(), expectedError, err, "they should be equal")
}

func (s *RepositoryTestSuite) Test_CreatePickupPoint_Failed_SetDefault() {
	sample := NewSamplePickupPoint()
	expectedError := gorm.ErrInvalidData

	s.mock.ExpectBegin()
	s.mock.ExpectQuery(`INSERT INTO "pickup_points"`).WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(sample.ID.ID))
	s.mock.ExpectExec(regexp.QuoteMeta(`UPDATE "pickup_points" SET "default"=$1,"updated_at"=$2 WHERE (workspace_id = $3 AND id <> $4) AND "pickup_points"."deleted_at" IS NULL`)).
		WithArgs(false, sqlmock.AnyArg(), sample.WorkspaceID, sample.ID.ID).
		WillReturnError(expectedError)
	s.mock.ExpectRollback()

	result, err := s.repo.CreatePickupPoint(s.ctx, &entities.PickupPoint{
		BaseEntity:  sample.BaseEntity,
		WorkspaceID: sample.WorkspaceID,
		AddressID:   sample.AddressID,
		FirstName:   sample.FirstName,
		LastName:    sample.LastName,
		PhoneNumber: sample.PhoneNumber,
		Default:     true,
	})
	require.Error(s.T(), err)
	assert.Nil(s.T(), result, "result should be nil")
	assert.Equal(s.T(), expectedError, err, "they should be equal")
}

func (s *RepositoryTestSuite) Test_UpdatePickupPoint_Success() {
	sample := NewSamplePickupPoint()
	sample2 := NewSamplePickupPoint()
	expectedNewAddressID := sample2.Address.ID.ID
	expectedNewCountryID := sample2.Address.CountryID

	updatedPickupPoint := &entities.PickupPoint{
		FirstName:   sample2.FirstName,
		LastName:    sample2.LastName,
		Email:       sample2.Email,
		PhoneNumber: sample2.PhoneNumber,
		Default:     sample2.Default,
		Deactivate:  sample2.Deactivate,
		Address:     sample2.Address,
	}

	s.mock.ExpectBegin()
	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "pickup_points" WHERE workspace_id = $1 AND id = $2 AND "pickup_points"."deleted_at" IS NULL ORDER BY "pickup_points"."id" LIMIT 1`)).
		WithArgs(sample.WorkspaceID, sample.ID.ID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "workspace_id", "address_id", "first_name", "last_name", "phone_number", "email", "default", "deactivate", "created_at", "updated_at"}).
			AddRow(
				sample.ID.ID,
				sample.WorkspaceID,
				sample.AddressID,
				sample.FirstName,
				sample.LastName,
				sample.PhoneNumber,
				sample.Email,
				sample.Default,
				sample.Deactivate,
				sample.CreatedAt,
				sample.UpdatedAt,
			))

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "addresses" WHERE "addresses"."id" = $1 AND "addresses"."deleted_at" IS NULL`)).
		WithArgs(sample.Address.ID.ID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "address_1", "address_2", "postal_code", "country_id", "deactivate", "unit_number", "created_at", "updated_at"}).
			AddRow(
				sample.Address.ID.ID,
				sample.Address.Address1,
				sample.Address.Address2,
				sample.Address.PostalCode,
				sample.Address.CountryID,
				sample.Address.Deactivate,
				sample.Address.UnitNumber,
				sample.Address.CreatedAt,
				sample.Address.UpdatedAt,
			))

	s.mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "address_sub_levels" WHERE "address_sub_levels"."address_id" = $1`)).
		WithArgs(sample.Address.ID.ID).
		WillReturnResult(sqlmock.NewResult(0, int64(len(sample.Address.SubLevelLocations))))

	s.mock.ExpectQuery(`INSERT INTO "addresses"`).
		WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(expectedNewAddressID))

	s.mock.ExpectQuery(`INSERT INTO "locations"`).
		WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(expectedNewCountryID))

	s.mock.ExpectExec(`INSERT INTO "address_sub_levels"`).
		WillReturnResult(driver.ResultNoRows)

	s.mock.ExpectExec(`UPDATE "pickup_points"`).
		WillReturnResult(driver.ResultNoRows)

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "pickup_points" WHERE "pickup_points"."deleted_at" IS NULL AND "id" = $1 LIMIT 1`)).
		WithArgs(sample.ID.ID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "workspace_id", "address_id", "first_name", "last_name", "phone_number", "email", "default", "deactivate", "created_at", "updated_at"}).
			AddRow(
				sample.ID.ID,
				sample.WorkspaceID,
				updatedPickupPoint.AddressID,
				updatedPickupPoint.FirstName,
				updatedPickupPoint.LastName,
				updatedPickupPoint.PhoneNumber,
				updatedPickupPoint.Email,
				updatedPickupPoint.Default,
				updatedPickupPoint.Deactivate,
				updatedPickupPoint.CreatedAt,
				updatedPickupPoint.UpdatedAt,
			))

	s.mock.ExpectExec(regexp.QuoteMeta(`UPDATE "pickup_points" SET "default"=$1,"updated_at"=$2 WHERE (workspace_id = $3 AND id <> $4) AND "pickup_points"."deleted_at" IS NULL`)).
		WithArgs(false, sqlmock.AnyArg(), sample.WorkspaceID, sample.ID.ID).
		WillReturnResult(driver.ResultNoRows)

	s.mock.ExpectCommit()

	err := s.repo.UpdatePickupPoint(s.ctx, sample.WorkspaceID, sample.ID, updatedPickupPoint)
	require.NoError(s.T(), err)
}

func (s *RepositoryTestSuite) Test_UpdatePickupPoint_Failed_NotFound() {
	sample := NewSamplePickupPoint()
	expectedError := gorm.ErrRecordNotFound

	s.mock.ExpectBegin()
	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "pickup_points" WHERE workspace_id = $1 AND id = $2 AND "pickup_points"."deleted_at" IS NULL ORDER BY "pickup_points"."id" LIMIT 1`)).
		WithArgs(sample.WorkspaceID, sample.ID.ID).
		WillReturnError(expectedError)
	s.mock.ExpectRollback()

	err := s.repo.UpdatePickupPoint(s.ctx, sample.WorkspaceID, sample.ID, &entities.PickupPoint{
		FirstName: "FN",
	})
	require.Error(s.T(), err)
	assert.Equal(s.T(), expectedError, err, "they should be equal")
}

func (s *RepositoryTestSuite) Test_UpdatePickupPoint_Failed_ClearSubLevelLocations() {
	sample := NewSamplePickupPoint()
	expectedError := gorm.ErrInvalidData

	s.mock.ExpectBegin()

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "pickup_points" WHERE workspace_id = $1 AND id = $2 AND "pickup_points"."deleted_at" IS NULL ORDER BY "pickup_points"."id" LIMIT 1`)).
		WithArgs(sample.WorkspaceID, sample.ID.ID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "workspace_id", "address_id", "first_name", "last_name", "phone_number", "email", "default", "deactivate", "created_at", "updated_at"}).
			AddRow(
				sample.ID.ID,
				sample.WorkspaceID,
				sample.AddressID,
				sample.FirstName,
				sample.LastName,
				sample.PhoneNumber,
				sample.Email,
				sample.Default,
				sample.Deactivate,
				sample.CreatedAt,
				sample.UpdatedAt,
			))

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "addresses" WHERE "addresses"."id" = $1 AND "addresses"."deleted_at" IS NULL`)).
		WithArgs(sample.Address.ID.ID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "address_1", "address_2", "postal_code", "country_id", "deactivate", "unit_number", "created_at", "updated_at"}).
			AddRow(
				sample.Address.ID.ID,
				sample.Address.Address1,
				sample.Address.Address2,
				sample.Address.PostalCode,
				sample.Address.CountryID,
				sample.Address.Deactivate,
				sample.Address.UnitNumber,
				sample.Address.CreatedAt,
				sample.Address.UpdatedAt,
			))

	s.mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "address_sub_levels" WHERE "address_sub_levels"."address_id" = $1`)).
		WithArgs(sample.Address.ID.ID).
		WillReturnError(expectedError)

	s.mock.ExpectRollback()

	err := s.repo.UpdatePickupPoint(s.ctx, sample.WorkspaceID, sample.ID, &entities.PickupPoint{
		FirstName: "FN",
	})
	require.Error(s.T(), err)
	assert.Equal(s.T(), expectedError, err, "they should be equal")
}

func (s *RepositoryTestSuite) Test_UpdatePickupPoint_Failed_Save() {
	sample := NewSamplePickupPoint()
	expectedNewAddressID := int64(2)
	expectedError := gorm.ErrInvalidData

	s.mock.ExpectBegin()

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "pickup_points" WHERE workspace_id = $1 AND id = $2 AND "pickup_points"."deleted_at" IS NULL ORDER BY "pickup_points"."id" LIMIT 1`)).
		WithArgs(sample.WorkspaceID, sample.ID.ID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "workspace_id", "address_id", "first_name", "last_name", "phone_number", "email", "default", "deactivate", "created_at", "updated_at"}).
			AddRow(
				sample.ID.ID,
				sample.WorkspaceID,
				sample.AddressID,
				sample.FirstName,
				sample.LastName,
				sample.PhoneNumber,
				sample.Email,
				sample.Default,
				sample.Deactivate,
				sample.CreatedAt,
				sample.UpdatedAt,
			))

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "addresses" WHERE "addresses"."id" = $1 AND "addresses"."deleted_at" IS NULL`)).
		WithArgs(sample.Address.ID.ID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "address_1", "address_2", "postal_code", "country_id", "deactivate", "unit_number", "created_at", "updated_at"}).
			AddRow(
				sample.Address.ID.ID,
				sample.Address.Address1,
				sample.Address.Address2,
				sample.Address.PostalCode,
				sample.Address.CountryID,
				sample.Address.Deactivate,
				sample.Address.UnitNumber,
				sample.Address.CreatedAt,
				sample.Address.UpdatedAt,
			))

	s.mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "address_sub_levels" WHERE "address_sub_levels"."address_id" = $1`)).
		WithArgs(sample.Address.ID.ID).
		WillReturnResult(sqlmock.NewResult(0, int64(len(sample.Address.SubLevelLocations))))

	s.mock.ExpectQuery(`INSERT INTO "addresses"`).
		WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(expectedNewAddressID))

	s.mock.ExpectExec(`UPDATE "pickup_points"`).WillReturnError(expectedError)

	s.mock.ExpectRollback()

	err := s.repo.UpdatePickupPoint(s.ctx, sample.WorkspaceID, sample.ID, &entities.PickupPoint{
		FirstName: "FN",
	})
	require.Error(s.T(), err)
	assert.Equal(s.T(), expectedError, err, "they should be equal")
}

func (s *RepositoryTestSuite) Test_UpdatePickupPoint_Failed_SetDefault() {
	sample := NewSamplePickupPoint()
	expectedNewAddressID := int64(2)
	expectedNewCountryID := int64(2)
	expectedError := gorm.ErrInvalidData
	updatedPickupPoint := &entities.PickupPoint{
		FirstName:   "FirstName",
		LastName:    "LastName",
		Email:       "newemail@domain.com",
		PhoneNumber: "0123456789",
		Default:     true,
		Deactivate:  false,
		Address: entities.Address{
			Address1:   "Address1",
			Address2:   "Address2",
			PostalCode: "10200",
			UnitNumber: "B2",
			SubLevelLocations: []entities.Location{{
				BaseEntity:        BaseEntityFromInt64(expectedNewAddressID),
				CountryID:         expectedNewCountryID,
				ParentID:          0,
				LocalizedName:     "LocalizedName",
				InternationalName: "InternationalName",
			}},
		},
	}

	s.mock.ExpectBegin()

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "pickup_points" WHERE workspace_id = $1 AND id = $2 AND "pickup_points"."deleted_at" IS NULL ORDER BY "pickup_points"."id" LIMIT 1`)).
		WithArgs(sample.WorkspaceID, sample.ID.ID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "workspace_id", "address_id", "first_name", "last_name", "phone_number", "email", "default", "deactivate", "created_at", "updated_at"}).
			AddRow(
				sample.ID.ID,
				sample.WorkspaceID,
				sample.AddressID,
				sample.FirstName,
				sample.LastName,
				sample.PhoneNumber,
				sample.Email,
				sample.Default,
				sample.Deactivate,
				sample.CreatedAt,
				sample.UpdatedAt,
			))

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "addresses" WHERE "addresses"."id" = $1 AND "addresses"."deleted_at" IS NULL`)).
		WithArgs(sample.Address.ID.ID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "address_1", "address_2", "postal_code", "country_id", "deactivate", "unit_number", "created_at", "updated_at"}).
			AddRow(
				sample.Address.ID.ID,
				sample.Address.Address1,
				sample.Address.Address2,
				sample.Address.PostalCode,
				sample.Address.CountryID,
				sample.Address.Deactivate,
				sample.Address.UnitNumber,
				sample.Address.CreatedAt,
				sample.Address.UpdatedAt,
			))

	s.mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "address_sub_levels" WHERE "address_sub_levels"."address_id" = $1`)).
		WithArgs(sample.Address.ID.ID).
		WillReturnResult(sqlmock.NewResult(0, int64(len(sample.Address.SubLevelLocations))))

	s.mock.ExpectQuery(`INSERT INTO "addresses"`).
		WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(expectedNewAddressID))

	s.mock.ExpectExec(`UPDATE "pickup_points"`).
		WillReturnResult(driver.ResultNoRows)

	s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "pickup_points" WHERE "pickup_points"."deleted_at" IS NULL AND "id" = $1 LIMIT 1`)).
		WithArgs(sample.ID.ID).
		WillReturnRows(sqlmock.NewRows([]string{"id", "workspace_id", "address_id", "first_name", "last_name", "phone_number", "email", "default", "deactivate", "created_at", "updated_at"}).
			AddRow(
				sample.ID.ID,
				sample.WorkspaceID,
				updatedPickupPoint.AddressID,
				updatedPickupPoint.FirstName,
				updatedPickupPoint.LastName,
				updatedPickupPoint.PhoneNumber,
				updatedPickupPoint.Email,
				updatedPickupPoint.Default,
				updatedPickupPoint.Deactivate,
				updatedPickupPoint.CreatedAt,
				updatedPickupPoint.UpdatedAt,
			))

	s.mock.ExpectExec(regexp.QuoteMeta(`UPDATE "pickup_points" SET "default"=$1,"updated_at"=$2 WHERE (workspace_id = $3 AND id <> $4) AND "pickup_points"."deleted_at" IS NULL`)).
		WithArgs(false, sqlmock.AnyArg(), sample.WorkspaceID, sample.ID.ID).
		WillReturnError(expectedError)

	s.mock.ExpectRollback()

	err := s.repo.UpdatePickupPoint(s.ctx, sample.WorkspaceID, sample.ID, &entities.PickupPoint{
		FirstName: "FN",
		Default:   true,
	})
	require.Error(s.T(), err)
	assert.Equal(s.T(), expectedError, err, "they should be equal")
}

func (s *RepositoryTestSuite) Test_DeletePickupPoint_Success() {
	sample := NewSamplePickupPoint()

	s.mock.ExpectBegin()
	s.mock.ExpectExec(regexp.QuoteMeta(`UPDATE "pickup_points" SET "deleted_at"=$1 WHERE "pickup_points"."id" = $2 AND "pickup_points"."deleted_at" IS NULL`)).
		WithArgs(sqlmock.AnyArg(), sample.ID.ID).
		WillReturnResult(driver.ResultNoRows)
	s.mock.ExpectCommit()

	err := s.repo.DeletePickupPoint(s.ctx, sample.WorkspaceID, sample.ID)
	require.NoError(s.T(), err)
}

func (s *RepositoryTestSuite) Test_DeletePickupPoint_Failed() {
	sample := NewSamplePickupPoint()
	expectedError := gorm.ErrInvalidData

	s.mock.ExpectBegin()
	s.mock.ExpectExec(regexp.QuoteMeta(`UPDATE "pickup_points" SET "deleted_at"=$1 WHERE "pickup_points"."id" = $2 AND "pickup_points"."deleted_at" IS NULL`)).
		WithArgs(sqlmock.AnyArg(), sample.ID.ID).
		WillReturnError(expectedError)
	s.mock.ExpectRollback()

	err := s.repo.DeletePickupPoint(s.ctx, sample.WorkspaceID, sample.ID)
	require.Error(s.T(), err)
	assert.Equal(s.T(), expectedError, err, "they should be equal")
}
