package address

import (
	"context"
	"strings"

	"gorm.io/gorm/clause"

	"github.com/UpmeshLTD/urus-aio/infrastructure/db/entities"
	"github.com/UpmeshLTD/urus-aio/mdm/db/gormdb"
)

type Repository interface {
	CreateAddress(ctx context.Context, address *entities.Address) (*entities.Address, error)
	UpdateAddress(ctx context.Context, id entities.ID, address *entities.Address) error
	DeleteAddress(ctx context.Context, id entities.ID) error
	GetAddressByID(ctx context.Context, id entities.ID) (*entities.Address, error)

	GetBuyerAddressByID(ctx context.Context, id entities.ID) (*entities.BuyerAddress, error)
	GetBuyerAddresses(ctx context.Context, params *entities.GetBuyerAddressByBuyerIDParams) ([]*entities.BuyerAddress, error)
	CreateBuyerAddress(ctx context.Context, entity *entities.BuyerAddress) (*entities.BuyerAddress, error)
	UpdateBuyerAddress(ctx context.Context, id entities.ID, entity *entities.BuyerAddress) (*entities.BuyerAddress, error)
	UpdateBuyerAddressDefaultStatus(ctx context.Context, id entities.ID, isDefault bool) error
	DeleteBuyerAddress(ctx context.Context, id entities.ID) error

	GetDraftCheckoutInfoByCartID(ctx context.Context, cartId string) (*entities.DraftCheckoutInfo, error)
	GetDraftCheckoutInfo(ctx context.Context, query *entities.DraftCheckoutInfo) (*entities.DraftCheckoutInfo, error)
	CreateDraftCheckoutInfo(ctx context.Context, params *entities.SaveDraftCheckoutInfoParams) (*entities.DraftCheckoutInfo, error)
	UpdateDraftCheckoutInfo(ctx context.Context, addressId entities.ID, params *entities.SaveDraftCheckoutInfoParams) (*entities.DraftCheckoutInfo, error)
	DeleteDraftCheckoutInfoByCartID(ctx context.Context, cartId string) error
}

type repositoryImpl struct {
	db gormdb.GormDB
}

func NewRepository(db gormdb.GormDB) Repository {
	return &repositoryImpl{
		db: db,
	}
}

// Address
func (r *repositoryImpl) CreateAddress(ctx context.Context, address *entities.Address) (*entities.Address, error) {
	if err := r.db.DB().Create(&address).Error; err != nil {
		return nil, err
	}
	return address, nil
}

func (r *repositoryImpl) UpdateAddress(ctx context.Context, id entities.ID, address *entities.Address) error {
	address.ID = id

	tx := r.db.DB().Begin()

	if err := tx.Model(&entities.Address{
		BaseEntity: entities.BaseEntity{
			ID: id,
		},
	}).Association("SubLevelLocations").Clear(); err != nil {
		tx.Rollback()
		return err
	}

	if err := tx.Preload("SubLevelLocations").Updates(&address).Error; err != nil {
		tx.Rollback()
		return err
	}

	// UPDATE OPTIONAL FIELDS
	var optionalFields = map[string]interface{}{
		"Address2": address.Address2,
	}
	if err := tx.Model(&entities.Address{
		BaseEntity: entities.BaseEntity{
			ID: id,
		},
	}).Updates(&optionalFields).Error; err != nil {
		tx.Rollback()
		return err
	}

	return tx.Commit().Error
}

func (r *repositoryImpl) DeleteAddress(ctx context.Context, id entities.ID) error {
	if err := r.db.DB().Delete(&entities.Address{BaseEntity: entities.BaseEntity{ID: id}}).Error; err != nil {
		return err
	}
	return nil
}

func (r *repositoryImpl) GetAddressByID(ctx context.Context, id entities.ID) (*entities.Address, error) {
	var err error
	res := &entities.Address{}

	if err = r.db.DB().Preload(clause.Associations).First(res, "id = ?", id.ToInt64()).Error; err != nil {
		return nil, err
	}
	return res, nil
}

// Buyer address
func (r *repositoryImpl) CreateBuyerAddress(ctx context.Context, entity *entities.BuyerAddress) (*entities.BuyerAddress, error) {
	if err := r.db.DB().Create(&entity).Error; err != nil {
		return nil, err
	}
	return r.GetBuyerAddressByID(ctx, entity.ID)
}

func (r *repositoryImpl) UpdateBuyerAddress(ctx context.Context, id entities.ID, entity *entities.BuyerAddress) (*entities.BuyerAddress, error) {
	if err := r.db.DB().Model(&entities.BuyerAddress{}).Where("id = ?", id.ToInt64()).Updates(&entity).Error; err != nil {
		return nil, err
	}
	return r.GetBuyerAddressByID(ctx, id)
}

func (r *repositoryImpl) UpdateBuyerAddressDefaultStatus(ctx context.Context, id entities.ID, isDefault bool) error {
	return r.db.DB().Model(&entities.BuyerAddress{}).Where("id = ?", id.ToInt64()).Update("is_default", isDefault).Error
}

func (r *repositoryImpl) DeleteBuyerAddress(ctx context.Context, id entities.ID) error {
	return r.db.DB().Delete(&entities.BuyerAddress{}, id).Error
}

func (r *repositoryImpl) GetBuyerAddressByID(ctx context.Context, id entities.ID) (*entities.BuyerAddress, error) {
	result := &entities.BuyerAddress{}
	if err := r.db.DB().Preload("Address").Preload("Address.SubLevelLocations").First(&result, id.ToInt64()).Error; err != nil {
		return nil, err
	}
	return result, nil
}

func (r *repositoryImpl) GetBuyerAddresses(ctx context.Context, params *entities.GetBuyerAddressByBuyerIDParams) ([]*entities.BuyerAddress, error) {
	var results []*entities.BuyerAddress
	builder := r.db.DB().Preload("Address").Preload("Address.SubLevelLocations").Where(&entities.BuyerAddress{BuyerID: params.BuyerID})

	if params.IsDefault {
		builder = builder.Where("is_default = true")
	}
	if params.Label != "" {
		builder = builder.Where("LOWER(label) like ?", strings.ToLower(params.Label))
	}

	if err := builder.Order("is_default desc, created_at desc").Find(&results).Error; err != nil {
		return nil, err
	}

	return results, nil
}

func (r *repositoryImpl) GetDraftCheckoutInfoByCartID(ctx context.Context, cartId string) (*entities.DraftCheckoutInfo, error) {
	var entity *entities.DraftCheckoutInfo
	if err := r.db.DB().Preload("Address").Preload("Address.SubLevelLocations").First(&entity, "cart_id = ?", cartId).Error; err != nil {
		return nil, err
	}
	return entity, nil
}

func (r *repositoryImpl) GetDraftCheckoutInfo(ctx context.Context, query *entities.DraftCheckoutInfo) (*entities.DraftCheckoutInfo, error) {
	var entity *entities.DraftCheckoutInfo
	if err := r.db.DB().Preload("Address").Preload("Address.SubLevelLocations").Where(query).First(&entity).Error; err != nil {
		return nil, err
	}
	return entity, nil
}

func (r *repositoryImpl) CreateDraftCheckoutInfo(ctx context.Context, params *entities.SaveDraftCheckoutInfoParams) (*entities.DraftCheckoutInfo, error) {
	address, err := r.CreateAddress(ctx, &entities.Address{
		Address1:          params.Address1,
		Address2:          params.Address2,
		PostalCode:        params.PostalCode,
		UnitNumber:        params.UnitNumber,
		CountryID:         params.CountryID,
		SubLevelLocations: params.SubLevelLocations,
		Deactivate:        false,
	})
	if err != nil {
		return nil, err
	}
	if err := r.db.DB().Create(&entities.DraftCheckoutInfo{
		CartID:               params.CartID,
		PhoneNumber:          params.PhoneNumber,
		DeliveryInstructions: params.DeliveryInstructions,
		Email:                params.Email,
		RecipientName:        params.RecipientName,
		AddressID:            address.ID.ToInt64(),
		BuyerID:              &params.BuyerID,
	}).Error; err != nil {
		return nil, err
	}

	return r.GetDraftCheckoutInfoByCartID(ctx, params.CartID)
}

func (r *repositoryImpl) UpdateDraftCheckoutInfo(ctx context.Context, addressId entities.ID, params *entities.SaveDraftCheckoutInfoParams) (*entities.DraftCheckoutInfo, error) {
	// Get entity
	entity, err := r.GetDraftCheckoutInfoByCartID(ctx, params.CartID)
	if err != nil {
		return nil, err
	}

	// Update draft checkout info
	if err = r.UpdateAddress(ctx, entities.IDFromInt64(entity.AddressID), &entities.Address{
		Address1:          params.Address1,
		Address2:          params.Address2,
		PostalCode:        params.PostalCode,
		UnitNumber:        params.UnitNumber,
		CountryID:         params.CountryID,
		SubLevelLocations: params.SubLevelLocations,
	}); err != nil {
		return nil, err
	}

	if err := r.db.DB().Where("cart_id = ?", entity.CartID).Updates(&entities.DraftCheckoutInfo{
		PhoneNumber:          params.PhoneNumber,
		DeliveryInstructions: params.DeliveryInstructions,
		Email:                params.Email,
		RecipientName:        params.RecipientName,
		WorkspaceID:          params.WorkspaceID,
	}).Error; err != nil {
		return nil, err
	}

	return r.GetDraftCheckoutInfoByCartID(ctx, params.CartID)
}

func (r *repositoryImpl) DeleteDraftCheckoutInfoByCartID(ctx context.Context, cartId string) error {
	return r.db.DB().Where("cart_id = ?", cartId).Unscoped().Delete(&entities.DraftCheckoutInfo{}).Error
}
