package pricing

import (
	"errors"
	"fmt"
	"strconv"
	"time"
)

type BuyingStrategy int

const (
	UNLIMITED BuyingStrategy = iota
	SUBSCRIPTION
	PAY_PER_USE
)

type Strategy interface {
	GetStrategy() string
	GetStrategyValue() int
}

type TimePricingStrategy int

const (
	ONCE TimePricingStrategy = iota
	PER_SECOND
	PER_MINUTE
	PER_HOUR
	PER_DAY
	PER_WEEK
	PER_MONTH
)

func (t TimePricingStrategy) GetStrategy() string {
	return [...]string{"ONCE", "PER_SECOND", "PER_MINUTE", "PER_HOUR", "PER_DAY", "PER_WEEK", "PER_MONTH"}[t]
}

func (t TimePricingStrategy) GetStrategyValue() int {
	return int(t)
}

func getAverageTimeInSecond(averageTimeInSecond float64, start time.Time, end *time.Time) float64 {
	now := time.Now()
	after := now.Add(time.Duration(averageTimeInSecond) * time.Second)

	fromAverageDuration := after.Sub(now).Seconds()
	var tEnd time.Time
	if end == nil {
		tEnd = start.Add(1 * time.Hour)
	} else {
		tEnd = *end
	}
	fromDateDuration := tEnd.Sub(start).Seconds()

	if fromAverageDuration > fromDateDuration {
		return fromAverageDuration
	}
	return fromDateDuration
}

func BookingEstimation(t TimePricingStrategy, price float64, locationDurationInSecond float64, start time.Time, end *time.Time) (float64, error) {
	locationDurationInSecond = getAverageTimeInSecond(locationDurationInSecond, start, end)
	priceStr := fmt.Sprintf("%v", price)
	p, err := strconv.ParseFloat(priceStr, 64)
	if err != nil {
		return 0, err
	}
	switch t {
	case ONCE:
		return p, nil
	case PER_HOUR:
		return p * float64(locationDurationInSecond/3600), nil
	case PER_MINUTE:
		return p * float64(locationDurationInSecond/60), nil
	case PER_SECOND:
		return p * locationDurationInSecond, nil
	case PER_DAY:
		return p * float64(locationDurationInSecond/86400), nil
	case PER_WEEK:
		return p * float64(locationDurationInSecond/604800), nil
	case PER_MONTH:
		return p * float64(locationDurationInSecond/2592000), nil
	}
	return 0, errors.New("Pricing strategy not found")
}

// hmmmm
type PricingStrategy[T Strategy] struct {
	Price               float64             `json:"Price" bson:"Price" default:"0"`                                 // Price is the Price of the pricing
	BuyingStrategy      BuyingStrategy      `json:"buying_strategy" bson:"buying_strategy" default:"0"`             // BuyingStrategy is the buying strategy of the pricing
	TimePricingStrategy TimePricingStrategy `json:"time_pricing_strategy" bson:"time_pricing_strategy" default:"0"` // TimePricingStrategy is the time pricing strategy of the pricing
	OverrideStrategy    T                   `json:"override_strategy" bson:"override_strategy" default:"-1"`        // Modulation is the modulation of the pricing
}

func (p PricingStrategy[T]) SetStrategy(Price float64, BuyingStrategy BuyingStrategy, TimePricingStrategy TimePricingStrategy) error {
	if TimePricingStrategy == ONCE && (BuyingStrategy != UNLIMITED || BuyingStrategy != PAY_PER_USE) {
		return errors.New("time pricing strategy can only be set to ONCE if buying strategy is UNLIMITED or PAY_PER_USE")
	} else if BuyingStrategy == SUBSCRIPTION && (TimePricingStrategy == ONCE) {
		return errors.New("subscription duration in second must be set if buying strategy is SUBSCRIPTION")
	}
	p.Price = Price
	p.BuyingStrategy = BuyingStrategy
	p.TimePricingStrategy = TimePricingStrategy
	return nil
}

func (p PricingStrategy[T]) SetSpecificPerUseStrategy(strategy T) error {
	if p.BuyingStrategy == UNLIMITED {
		return errors.New("UNLIMITED buying strategy can't have a specific strategy, Price is set on buying")
	}
	p.OverrideStrategy = strategy
	return nil
}

// QUANTITY can be how many of gb core per example
func (p PricingStrategy[T]) GetPrice(amountOfData float64, bookingTimeDuration float64, start time.Time, end *time.Time) (float64, error) {
	if p.BuyingStrategy == SUBSCRIPTION {
		return BookingEstimation(p.GetTimePricingStrategy(), p.Price*float64(amountOfData), bookingTimeDuration, start, end)
	} else if p.BuyingStrategy == UNLIMITED {
		return p.Price, nil
	}
	return p.Price * float64(amountOfData), nil
}

func (p PricingStrategy[T]) GetBuyingStrategy() BuyingStrategy {
	return p.BuyingStrategy
}

func (p PricingStrategy[T]) GetTimePricingStrategy() TimePricingStrategy {
	return p.TimePricingStrategy
}

func (p PricingStrategy[T]) GetOverrideStrategy() T {
	return p.OverrideStrategy
}