package pricing

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

type BuyingStrategy int

const (
	UNLIMITED BuyingStrategy = iota
	SUBSCRIPTION
	PAY_PER_USE
)

func (t BuyingStrategy) String() string {
	return [...]string{"UNLIMITED", "SUBSCRIPTION", "PAY PER USE"}[t]
}

func BuyingStrategyList() []BuyingStrategy {
	return []BuyingStrategy{UNLIMITED, 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) String() string {
	return [...]string{"ONCE", "PER SECOND", "PER MINUTE", "PER HOUR", "PER DAY", "PER WEEK", "PER MONTH"}[t]
}

func TimePricingStrategyList() []TimePricingStrategy {
	return []TimePricingStrategy{ONCE, 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")
}

type PricingStrategy[T Strategy] struct {
	Price               float64             `json:"price" bson:"price" default:"0"`                                 // Price is the Price of the pricing
	Currency            string              `json:"currency" bson:"currency" default:"USD"`                         // Currency is the currency 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]) 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
}
