package fulfillment

import (
	"context"
	"fmt"
	"github.com/UpmeshLTD/urus-aio/infrastructure/db/entities"
	"github.com/UpmeshLTD/urus-aio/infrastructure/enums"
	e "github.com/UpmeshLTD/urus-aio/infrastructure/errors"
	m "github.com/UpmeshLTD/urus-aio/infrastructure/errors/messages"
	"github.com/UpmeshLTD/urus-aio/infrastructure/utils"
	"github.com/UpmeshLTD/urus-aio/paid-order/service/dto"
	"math"
	"time"
)

type Service interface {
	GetList(ctx context.Context, workspaceID string, req *dto.GetFulfillmentsRequest) ([]*entities.Fulfillment, int64, error)
	Get(ctx context.Context, id string) (*entities.Fulfillment, error)
	GetWithAssociates(ctx context.Context, id string) (*entities.Fulfillment, error)

	Create(ctx context.Context, fulfillment *entities.Fulfillment) (*entities.Fulfillment, error)
	Update(ctx context.Context, entity *entities.Fulfillment, req *entities.Fulfillment) error
	UpdateDraftStatus(ctx context.Context, id string, draft bool) error
	Delete(ctx context.Context, id string) error
	DeleteBatch(ctx context.Context, ids []string) error

	GetFulfillmentEstimatedFees(ctx context.Context, courierDeliveryPlanID int64) []*entities.FulfillmentEstimatedFee
	CalculateEstimatedFee(fees []*entities.FulfillmentEstimatedFee, dm *entities.WeightDimension) float64
}

type serviceImpl struct {
	repo Repository
}

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

func (s *serviceImpl) GetList(ctx context.Context, workspaceID string, req *dto.GetFulfillmentsRequest) ([]*entities.Fulfillment, int64, error) {
	return s.repo.GetList(ctx, workspaceID, req)
}

func (s *serviceImpl) Get(ctx context.Context, id string) (*entities.Fulfillment, error) {
	return s.repo.Get(ctx, id)
}

func (s *serviceImpl) GetWithAssociates(ctx context.Context, id string) (*entities.Fulfillment, error) {
	fulfillment, err := s.repo.GetWithAssociates(ctx, id)
	if err != nil {
		return nil, err
	}
	if fulfillment.CourierDeliveryPlanID != nil && fulfillment.CourierID != nil && *fulfillment.CourierID == entities.CourierIdNinjaVanSG {
		fees := s.GetFulfillmentEstimatedFees(ctx, *fulfillment.CourierDeliveryPlanID)
		for i, v := range fulfillment.PaidOrders {
			fulfillment.PaidOrders[i].EstimatedFee = s.CalculateEstimatedFee(fees, &v.Dimension)
		}
	}
	return fulfillment, nil
}

func (s *serviceImpl) Create(ctx context.Context, fulfillment *entities.Fulfillment) (*entities.Fulfillment, error) {
	return s.repo.Create(ctx, fulfillment)
}

func (s *serviceImpl) Update(ctx context.Context, entity *entities.Fulfillment, req *entities.Fulfillment) error {
	maps := req.ToMap()

	// Validate pickup time if courier are J&T Thai, Malaysia, Singapore
	if req.Step == entities.FulfillmentStepPickupDetails {
		courierID := int64(0)
		if entity.CourierID != nil {
			courierID = *entity.CourierID
		}
		if courierID == entities.CourierIdJTExpressTH ||
			courierID == entities.CourierIdJTExpressMY ||
			courierID == entities.CourierIdJTExpressSG {
			if req.PickupStartTime != nil && req.PickupEndTime != nil {
				if err := s.validatePickupTime(ctx, courierID, req); err != nil {
					return err
				}
			} else {
				req.PickupStartTime = nil
				req.PickupEndTime = nil
			}
		}

		maps["PickupInstruction"] = req.PickupInstruction
		maps["PickupStartTime"] = req.PickupStartTime
		maps["PickupEndTime"] = req.PickupEndTime
	}

	return s.repo.Update(ctx, entity.ID.String(), maps)
}

func (s *serviceImpl) Delete(ctx context.Context, id string) error {
	return s.repo.Delete(ctx, id)
}

func (s *serviceImpl) DeleteBatch(ctx context.Context, ids []string) error {
	return s.repo.DeleteBatch(ctx, ids)
}

func (s *serviceImpl) UpdateDraftStatus(ctx context.Context, id string, draft bool) error {
	return s.repo.Update(ctx, id, map[string]interface{}{
		"Draft": draft,
	})
}

func (s *serviceImpl) validatePickupTime(ctx context.Context, courierID int64, fulfillment *entities.Fulfillment) error {
	switch courierID {
	case entities.CourierIdJTExpressTH:
		return s.validatePickupTimeJntThai(ctx, fulfillment)
	case entities.CourierIdJTExpressMY:
		return s.validatePickupTimeJntMalaysia(ctx, fulfillment)
	case entities.CourierIdJTExpressSG:
		return s.validatePickupTimeJntSingapore(ctx, fulfillment)
	}
	return nil
}

func (s *serviceImpl) validatePickupTimeJntThai(ctx context.Context, fulfillment *entities.Fulfillment) error {
	if fulfillment.PickupStartTime == nil {
		return e.New(ctx, e.Other, m.PickupStartTimeInvalid).ToError()
	}

	if fulfillment.PickupEndTime == nil {
		return e.New(ctx, e.Other, m.PickupEndTimeInvalid).ToError()
	}

	location, err := time.LoadLocation(enums.TimezoneTH)
	if err != nil {
		fmt.Printf("[ValidatePickupTimeJntThai] Failed to load location: %s\n", err)
		return e.New(ctx, e.InternalServer, m.FailedToLoadLocationTimeZone).ToError()
	}

	t := time.Now()
	now := time.Now().In(location)
	startTime := fulfillment.PickupStartTime.In(location)
	endTime := fulfillment.PickupEndTime.In(location)

	fmt.Printf("[validatePickupTimeJntThai] System Time: %s\n", t)
	fmt.Printf("[validatePickupTimeJntThai] UTC Time: %s\n", t.UTC())
	fmt.Printf("[validatePickupTimeJntThai] PickupStartTime: %s\n", fulfillment.PickupStartTime)
	fmt.Printf("[validatePickupTimeJntThai] PickupEndTime: %s\n", fulfillment.PickupEndTime)
	fmt.Printf("[validatePickupTimeJntThai] System Time (%s): %s\n", location, now)
	fmt.Printf("[validatePickupTimeJntThai] PickupStartTime (%s): %s\n", location, startTime)
	fmt.Printf("[validatePickupTimeJntThai] PickupEndTime (%s): %s\n", location, endTime)

	// Check start time
	if startTime.Before(now) {
		return e.New(ctx, e.Other, m.PickupStartTimeInvalid).ToError()
	}
	if !utils.IsTimeBetween9AMTo6PM(startTime) {
		return e.New(ctx, e.Other, m.PickupStartTimeOutOfRange).ToError()
	}

	// Check end time
	if endTime.Before(startTime) {
		return e.New(ctx, e.Other, m.PickupEndTimeInvalid).ToError()
	}
	if !utils.IsTimeBetween9AMTo6PM(endTime) {
		return e.New(ctx, e.Other, m.PickupEndTimeOutOfRange).ToError()
	}

	return nil
}

func (s *serviceImpl) validatePickupTimeJntMalaysia(ctx context.Context, fulfillment *entities.Fulfillment) error {
	// Check start time
	if fulfillment.PickupStartTime == nil || fulfillment.PickupStartTime.Before(time.Now().UTC()) {
		return e.New(ctx, e.Other, m.PickupStartTimeInvalid).ToError()
	}

	// Check end time
	if fulfillment.PickupEndTime == nil || fulfillment.PickupEndTime.Before(*fulfillment.PickupStartTime) {
		return e.New(ctx, e.Other, m.PickupEndTimeInvalid).ToError()
	}

	return nil
}

func (s *serviceImpl) validatePickupTimeJntSingapore(ctx context.Context, fulfillment *entities.Fulfillment) error {
	if fulfillment.PickupStartTime == nil {
		return e.New(ctx, e.Other, m.PickupStartTimeInvalid).ToError()
	}

	if fulfillment.PickupEndTime == nil {
		return e.New(ctx, e.Other, m.PickupEndTimeInvalid).ToError()
	}

	location, err := time.LoadLocation(enums.TimezoneSG)
	if err != nil {
		fmt.Printf("[validatePickupTimeJntSingapore] Failed to load location: %s\n", err)
		return e.New(ctx, e.InternalServer, m.FailedToLoadLocationTimeZone).ToError()
	}

	t := time.Now()
	now := t.In(location)
	currentDate := now.Format("2006-01-02")
	startTime := fulfillment.PickupStartTime.In(location)
	startDate := startTime.Format("2006-01-02")
	endTime := fulfillment.PickupEndTime.In(location)
	endDate := endTime.Format("2006-01-02")
	timeslot := fmt.Sprintf("%d-%d", startTime.Hour(), endTime.Hour())
	dailyTimeslots := []string{"9-18", "9-12", "12-15", "15-18", "18-21"}
	saturdayTimeslots := []string{"9-13"}

	fmt.Printf("[validatePickupTimeJntSingapore] System Time: %s\n", t)
	fmt.Printf("[validatePickupTimeJntSingapore] UTC Time: %s\n", t.UTC())
	fmt.Printf("[validatePickupTimeJntSingapore] PickupStartTime: %s\n", fulfillment.PickupStartTime)
	fmt.Printf("[validatePickupTimeJntSingapore] PickupEndTime: %s\n", fulfillment.PickupEndTime)
	fmt.Printf("[validatePickupTimeJntSingapore] System Time (%s): %s\n", location, now)
	fmt.Printf("[validatePickupTimeJntSingapore] PickupStartTime (%s): %s\n", location, startTime)
	fmt.Printf("[validatePickupTimeJntSingapore] PickupEndTime (%s): %s\n", location, endTime)

	if endDate != startDate {
		return e.New(ctx, e.Other, m.PickupEndTimeNotSameStartDate).ToError()
	}
	if endTime.Before(startTime) {
		return e.New(ctx, e.Other, m.PickupTimeslotInvalid).ToError()
	}
	if utils.IsTimeOnSunday(endTime) {
		return e.New(ctx, e.Other, m.PickupTimeOnWeekend).ToError()
	}
	if utils.IsTimeOnSaturday(endTime) {
		// Saturday
		if !utils.InArray(timeslot, saturdayTimeslots) {
			return e.New(ctx, e.Other, m.PickupTimeslotInvalid).ToError()
		}
	} else {
		// Monday to Friday
		if !utils.InArray(timeslot, dailyTimeslots) {
			return e.New(ctx, e.Other, m.PickupTimeslotInvalid).ToError()
		}
	}
	if startDate == currentDate {
		cutOffHour := 0
		switch timeslot {
		case "9-12":
			cutOffHour = 8
		case "9-13", "12-15":
			cutOffHour = 11
		case "9-18", "15-18", "18-21":
			cutOffHour = 16
		}
		if cutOffHour > 0 {
			cutOffTime := time.Date(t.Year(), t.Month(), t.Day(), cutOffHour, 0, 0, 0, location)
			fmt.Printf("[validatePickupTimeJntSingapore] CutOffTime (%s): %s\n", location, cutOffTime)
			if now.After(cutOffTime) {
				return e.New(ctx, e.Other, m.PickupTimeslotUnavailable).ToError()
			}
		}
	}

	return nil
}

func (s *serviceImpl) GetFulfillmentEstimatedFees(ctx context.Context, courierDeliveryPlanID int64) []*entities.FulfillmentEstimatedFee {
	res, err := s.repo.GetFulfillmentEstimatedFees(ctx, courierDeliveryPlanID)
	if err == nil {
		return res
	}
	return make([]*entities.FulfillmentEstimatedFee, 0)
}

func (s *serviceImpl) CalculateEstimatedFee(fees []*entities.FulfillmentEstimatedFee, dm *entities.WeightDimension) float64 {
	if fees == nil || len(fees) == 0 {
		return 0
	}
	if dm.Width == nil || dm.Height == nil || dm.Length == nil {
		return 0
	}
	if dm.Weight <= 0 || *dm.Width <= 0 || *dm.Height <= 0 || *dm.Length <= 0 {
		return 0
	}

	wi := -1
	di := -1
	dimension := *dm.Width + *dm.Height + *dm.Length
	for i, v := range fees {
		if dm.Weight < v.MaxWeight && wi == -1 {
			wi = i
		}
		if dimension < v.MaxDimension && di == -1 {
			di = i
		}
	}
	if wi == -1 || di == -1 {
		return 0
	}
	if di >= wi {
		return math.Round(fees[di].Fee*100) / 100
	}
	if wi >= di {
		return math.Round(fees[wi].Fee*100) / 100
	}
	return 0
}
