package pricing

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

type BillingStrategy int // BAM BAM

// should except... on
const (
	BILL_ONCE BillingStrategy = iota // is a permanent buying ( predictible )
	BILL_PER_WEEK
	BILL_PER_MONTH
	BILL_PER_YEAR
)

func (t BillingStrategy) IsBillingStrategyAllowed(bs int) (BillingStrategy, bool) {
	switch t {
	case BILL_ONCE:
		return BILL_ONCE, bs == 0
	case BILL_PER_WEEK:
	case BILL_PER_MONTH:
	case BILL_PER_YEAR:
		return t, bs != 0
	}
	return t, false
}

func (t BillingStrategy) String() string {
	return [...]string{"BILL_ONCE", "BILL_PER_WEEK", "BILL_PER_MONTH", "BILL_PER_YEAR"}[t]
}

func BillingStrategyList() []BillingStrategy {
	return []BillingStrategy{BILL_ONCE, BILL_PER_WEEK, BILL_PER_MONTH, BILL_PER_YEAR}
}

type BuyingStrategy int

// should except... on
const (
	SUBSCRIPTION           BuyingStrategy = iota // is a permanent buying ( predictible )
	UNDEFINED_SUBSCRIPTION                       // a endless subscription ( unpredictible )
	PERMANENT                                    // a defined subscription ( predictible )
	// PAY_PER_USE                                  // per request. ( unpredictible )
)

func (t BuyingStrategy) String() string {
	return [...]string{"SUBSCRIPTION", "UNDEFINED_SUBSCRIPTION", "PERMANENT"}[t]
}

func (t BuyingStrategy) IsBillingStrategyAllowed(bs BillingStrategy) (BillingStrategy, bool) {
	switch t {
	case PERMANENT:
		return BILL_ONCE, bs == BILL_ONCE
	case UNDEFINED_SUBSCRIPTION:
		return BILL_PER_MONTH, bs != BILL_ONCE
	case SUBSCRIPTION:
		/*case PAY_PER_USE:
		return bs, true*/
	}
	return bs, false
}

func BuyingStrategyList() []BuyingStrategy {
	return []BuyingStrategy{SUBSCRIPTION, UNDEFINED_SUBSCRIPTION, PERMANENT}
}

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 TimePricingStrategyListStr() []string {
	return []string{"ONCE", "PER SECOND", "PER MINUTE", "PER HOUR", "PER DAY", "PER WEEK", "PER MONTH"}
}

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
	fromDateDuration := float64(0)
	if end != nil {
		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)
	if locationDurationInSecond <= 0 {
		return 0, nil
	}
	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")
}

// may suppress in pricing strategy -> to set in map
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

	// NO NEED ?
	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]) GetPriceHT(amountOfData float64, bookingTimeDuration float64, start time.Time, end *time.Time, variations []*PricingVariation) (float64, error) {
	switch p.BuyingStrategy {
	case SUBSCRIPTION:
		price, err := BookingEstimation(p.GetTimePricingStrategy(), p.Price*float64(amountOfData), bookingTimeDuration, start, end)
		if err != nil {
			return 0, err
		}
		if variations != nil {
			for _, v := range variations {
				price = v.GetPriceHT(price)
			}
			return price, nil
		}

		return p.Price, nil
	case PERMANENT:
		if variations != nil {
			price := p.Price
			for _, v := range variations {
				price = v.GetPriceHT(price)
			}
			return price, nil
		}
		return p.Price, nil
	}
	if variations != nil {
		price := p.Price
		for _, v := range variations {
			price = v.GetPriceHT(price)
		}
		return 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
}

type PricingVariation struct {
	Inflate    bool    `json:"inflate" bson:"price"`   // Price is the Price of the pricing
	Percentage float64 `json:"percent" bson:"percent"` // Currency is the currency of the pricing        // Modulation is the modulation of the pricing
	Priority   int     `json:"priority" bson:"priority"`
}

func (pv *PricingVariation) GetPriceHT(priceHT float64) float64 {
	value := (priceHT * pv.Percentage) / 100
	if pv.Inflate {
		return priceHT + value
	} else {
		return priceHT - value
	}
}
