package address

import (
	"context"
	"errors"
	"fmt"
	"log"
	"strings"

	"gorm.io/gorm"

	"github.com/UpmeshLTD/urus-aio/infrastructure/db/entities"
	"github.com/UpmeshLTD/urus-aio/mdm/server/http/dto"
	"github.com/UpmeshLTD/urus-aio/mdm/service/transform"
)

type Service 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)

	CreateBuyerAddress(ctx context.Context, params *entities.CreateBuyerAddressParams) (*entities.BuyerAddress, error)
	UpdateBuyerAddress(ctx context.Context, params *entities.UpdateBuyerAddressParams) (*entities.BuyerAddress, error)
	DeleteBuyerAddress(ctx context.Context, params *entities.DeleteBuyerAddressParams) error
	GetBuyerAddressByID(ctx context.Context, params *entities.GetBuyerAddressByIDParams) (*entities.BuyerAddress, error)
	GetBuyerAddressByBuyerID(ctx context.Context, buyerID string) ([]*entities.BuyerAddress, error)

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

type serviceImpl struct {
	repo Repository
}

func NewService(repo Repository) Service {
	return &serviceImpl{
		repo: repo,
	}
}

func (s *serviceImpl) CreateAddress(ctx context.Context, address *entities.Address) (*entities.Address, error) {
	return s.repo.CreateAddress(ctx, address)
}

func (s *serviceImpl) UpdateAddress(ctx context.Context, id entities.ID, address *entities.Address) error {
	return s.repo.UpdateAddress(ctx, id, address)
}

func (s *serviceImpl) DeleteAddress(ctx context.Context, id entities.ID) error {
	return s.repo.DeleteAddress(ctx, id)
}

func (s *serviceImpl) GetAddressByID(ctx context.Context, id entities.ID) (*entities.Address, error) {
	return s.repo.GetAddressByID(ctx, id)
}

// Buyer address
func (s *serviceImpl) CreateBuyerAddress(ctx context.Context, params *entities.CreateBuyerAddressParams) (*entities.BuyerAddress, error) {
	// Check existed label for `home` and `work` label
	// If existed then return error
	buyerAddresses, err := s.repo.GetBuyerAddresses(ctx, &entities.GetBuyerAddressByBuyerIDParams{BuyerID: params.BuyerID})
	if err != nil {
		log.Printf("[CreateBuyerAddress] Get existed buyer address failed: %s", err.Error())
		return nil, err
	}

	var defaultAddress *entities.BuyerAddress

	for _, v := range buyerAddresses {
		if strings.EqualFold(v.Label, params.Label) {
			log.Printf("[CreateBuyerAddress] Address for `home` and `work` label is existed")
			return nil, fmt.Errorf(fmt.Sprintf("Address label named %s has already exists!", params.Label))
		}
		if v.IsDefault {
			defaultAddress = v
		}
	}

	address, err := s.repo.CreateAddress(ctx, transform.CreateAddressRequestDTOToEntity(&dto.CreateAddressRequest{
		CountryCode:         params.CountryCode,
		Address1:            params.Address1,
		Address2:            params.Address2,
		PostalCode:          params.PostalCode,
		UnitNumber:          params.UnitNumber,
		SubLevelLocationIDs: params.SubLevelLocationIDs,
	}))
	if err != nil {
		return nil, err
	}

	if params.IsDefault && defaultAddress != nil {
		if err := s.repo.UpdateBuyerAddressDefaultStatus(ctx, defaultAddress.ID, false); err != nil {
			return nil, err
		}
	}

	if len(buyerAddresses) == 0 {
		params.IsDefault = true
	}

	result, err := s.repo.CreateBuyerAddress(ctx, &entities.BuyerAddress{
		AddressID:     address.ID.ToInt64(),
		BuyerID:       params.BuyerID,
		Label:         params.Label,
		Phone:         params.Phone,
		IsDefault:     params.IsDefault,
		RecipientName: params.RecipientName,
	})
	if err != nil {
		return nil, err
	}

	return s.repo.GetBuyerAddressByID(ctx, result.ID)
}

func (s *serviceImpl) UpdateBuyerAddress(ctx context.Context, params *entities.UpdateBuyerAddressParams) (*entities.BuyerAddress, error) {
	// Get buyer address & check authentication
	buyerAddress, err := s.GetBuyerAddressByID(ctx, &entities.GetBuyerAddressByIDParams{
		ID:      params.ID,
		BuyerID: params.BuyerID,
	})
	if err != nil {
		return nil, err
	}
	if buyerAddress.BuyerID != params.BuyerID {
		return nil, gorm.ErrRecordNotFound
	}

	if !strings.EqualFold(buyerAddress.Label, params.Label) {
		bas, err := s.repo.GetBuyerAddresses(ctx, &entities.GetBuyerAddressByBuyerIDParams{
			BuyerID: params.BuyerID,
			Label:   params.Label,
		})
		if err != nil {
			return nil, err
		}
		if len(bas) > 0 {
			return nil, fmt.Errorf(fmt.Sprintf("Address label named %s has already exists!", params.Label))
		}
	}

	// Update address
	if err := s.repo.UpdateAddress(ctx, entities.ID{ID: buyerAddress.AddressID}, transform.UpdateAddressRequestDTOToEntity(&dto.UpdateAddressRequest{
		Address1:            params.Address1,
		Address2:            params.Address2,
		PostalCode:          params.PostalCode,
		UnitNumber:          params.UnitNumber,
		SubLevelLocationIDs: params.SubLevelLocationIDs,
		Deactivate:          params.Deactivate,
	})); err != nil {
		return nil, err
	}

	if !buyerAddress.IsDefault && params.IsDefault {
		buyerAddresses, err := s.repo.GetBuyerAddresses(ctx, &entities.GetBuyerAddressByBuyerIDParams{
			BuyerID:   params.BuyerID,
			IsDefault: true,
		})
		if err != nil {
			return nil, err
		}

		if len(buyerAddresses) > 0 {
			for _, ba := range buyerAddresses {
				if err := s.repo.UpdateBuyerAddressDefaultStatus(ctx, ba.ID, false); err != nil {
					return nil, err
				}
			}
		}

		if err := s.repo.UpdateBuyerAddressDefaultStatus(ctx, buyerAddress.ID, true); err != nil {
			return nil, err
		}
	}

	return s.repo.UpdateBuyerAddress(ctx, entities.IDFromInt64(params.ID), &entities.BuyerAddress{
		Label:         params.Label,
		Phone:         params.Phone,
		RecipientName: params.RecipientName,
	})
}

func (s *serviceImpl) DeleteBuyerAddress(ctx context.Context, params *entities.DeleteBuyerAddressParams) error {
	// Get buyer address & check authentication
	buyerAddress, err := s.GetBuyerAddressByID(ctx, &entities.GetBuyerAddressByIDParams{
		ID:      params.ID,
		BuyerID: params.BuyerID,
	})
	if err != nil {
		return err
	}

	if err := s.repo.DeleteAddress(ctx, entities.ID{ID: buyerAddress.AddressID}); err != nil {
		return err
	}

	if err := s.repo.DeleteBuyerAddress(ctx, entities.IDFromInt64(params.ID)); err != nil {
		log.Printf("[DeleteBuyerAddress] Failed in deleting buyer address: %s\n", err.Error())
		return err
	}
	return nil
}

func (s *serviceImpl) GetBuyerAddressByID(ctx context.Context, params *entities.GetBuyerAddressByIDParams) (*entities.BuyerAddress, error) {
	// Get buyer address
	buyerAddress, err := s.repo.GetBuyerAddressByID(ctx, entities.IDFromInt64(params.ID))
	if err != nil {
		log.Printf("[GetBuyerAddressByID] Get buyer address failed: %s\n", err.Error())
		return nil, err
	}

	// Check authentication
	if buyerAddress.BuyerID != params.BuyerID {
		log.Println("[GetBuyerAddressByID] record not found")
		return nil, gorm.ErrRecordNotFound
	}

	return buyerAddress, nil
}

func (s *serviceImpl) GetBuyerAddressByBuyerID(ctx context.Context, buyerID string) ([]*entities.BuyerAddress, error) {
	return s.repo.GetBuyerAddresses(ctx, &entities.GetBuyerAddressByBuyerIDParams{BuyerID: buyerID})
}

// draft checkout info
func (s *serviceImpl) GetDraftCheckoutInfoByCartID(ctx context.Context, cartId string) (*entities.DraftCheckoutInfo, error) {
	entity, err := s.repo.GetDraftCheckoutInfoByCartID(ctx, cartId)
	if err != nil {
		log.Println("[GetDraftCheckoutInfoByCartID] error: " + err.Error())
		return nil, err
	}
	return entity, nil
}
func (s *serviceImpl) GetDraftCheckoutInfo(ctx context.Context, query *entities.DraftCheckoutInfo) (*entities.DraftCheckoutInfo, error) {
	entity, err := s.repo.GetDraftCheckoutInfo(ctx, query)
	if err != nil {
		log.Println("[GetDraftCheckoutInfoByCartID] error: " + err.Error())
		return nil, err
	}
	return entity, nil
}

func (s *serviceImpl) SaveDraftCheckoutInfo(ctx context.Context, params *entities.SaveDraftCheckoutInfoParams) (*entities.DraftCheckoutInfo, error) {
	entity, err := s.GetDraftCheckoutInfo(ctx, &entities.DraftCheckoutInfo{
		CartID:  params.CartID,
		BuyerID: &params.BuyerID,
	})
	if err != nil {
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, err
		} else { // Record not found. Create data!
			// Check country code
			if params.CountryID == 0 {
				err := fmt.Errorf("country id is required")
				log.Println("[SaveDraftCheckoutInfo] error: " + err.Error())
				return nil, err
			}
			entity, err := s.repo.CreateDraftCheckoutInfo(ctx, params)
			if err != nil {
				log.Println("[SaveDraftCheckoutInfo] error: " + err.Error())
				return nil, err
			}
			return entity, nil
		}
	}
	// Record found. Update data!
	// Only allow to update WorkspaceID if the record not have data
	if entity.WorkspaceID != nil && *entity.WorkspaceID != "" {
		params.WorkspaceID = nil
	}
	entity, err = s.repo.UpdateDraftCheckoutInfo(ctx, entities.IDFromInt64(entity.AddressID), params)
	if err != nil {
		log.Println("[SaveDraftCheckoutInfo] error: " + err.Error())
		return nil, err
	}
	return entity, nil
}

func (s *serviceImpl) DeleteDraftCheckoutInfoByCartID(ctx context.Context, cartId string) error {
	if err := s.repo.DeleteDraftCheckoutInfoByCartID(ctx, cartId); err != nil {
		log.Println("[DeleteDraftCheckoutInfoByCartID] error: " + err.Error())
		return err
	}
	return nil
}
