package subscriptions

import (
	"errors"
	"time"

	"code.secondbit.org/uuid.hg"
)

const (
	// MonthlyPeriod represents a period of once a month.
	MonthlyPeriod period = "monthly"
)

var (
	// ErrSubscriptionAlreadyExists is returned when a Subscription
	// with an identical ID already exists in the subscriptionStore.
	ErrSubscriptionAlreadyExists = errors.New("Subscription already exists")
	// ErrSubscriptionNotFound is returned when a single Subscription
	// is acted upon or requested, but cannot be found.
	ErrSubscriptionNotFound = errors.New("Subscription not found")
	// ErrStripeCustomerAlreadyExists is returned when a Subscription
	// is created or updates its StripeCustomer property, but that
	// StripeCustomer is already associated with another Subscription.
	ErrStripeCustomerAlreadyExists = errors.New("Stripe customer already assigned to another Subscription")
	// ErrSubscriptionChangeEmpty is returned when a SubscriptionChange
	// is empty but is passed to subscriptionStore.UpdateSubscription
	// anyways.
	ErrSubscriptionChangeEmpty = errors.New("SubscriptionChange is empty")
	// ErrNoSubscriptionID is returned when one or more Subscription IDs
	// are required, but none are provided.
	ErrNoSubscriptionID = errors.New("no Subscription ID provided")
)

type period string

// Subscription represents the state of a user's payments. It holds
// metadata about the last time a user was charged, how much a user
// should be charged, how to charge a user and how much to charge
// the user.
type Subscription struct {
	ID             uuid.ID
	UserID         uuid.ID
	StripeCustomer string
	Amount         int
	Period         period
	Created        time.Time
	BeginCharging  time.Time
	LastCharged    time.Time
	LastNotified   time.Time
	InLockout      bool
}

// SubscriptionChange represents desired changes to a Subscription
// object. A nil value means that property should remain unchanged.
type SubscriptionChange struct {
	StripeCustomer *string
	Amount         *int
	Period         *period
	BeginCharging  *time.Time
	LastCharged    *time.Time
	LastNotified   *time.Time
	InLockout      *bool
}

// IsEmpty returns true if the SubscriptionChange doesn't request
// a change to any property of the Subscription.
func (change SubscriptionChange) IsEmpty() bool {
	if change.StripeCustomer != nil {
		return false
	}
	if change.Amount != nil {
		return false
	}
	if change.Period != nil {
		return false
	}
	if change.BeginCharging != nil {
		return false
	}
	if change.LastCharged != nil {
		return false
	}
	if change.LastNotified != nil {
		return false
	}
	if change.InLockout != nil {
		return false
	}
	return true
}

// ApplyChange updates a Subscription based on the changes requested
// by a SubscriptionChange.
func (s *Subscription) ApplyChange(change SubscriptionChange) {
	if change.StripeCustomer != nil {
		s.StripeCustomer = *change.StripeCustomer
	}
	if change.Amount != nil {
		s.Amount = *change.Amount
	}
	if change.Period != nil {
		s.Period = *change.Period
	}
	if change.BeginCharging != nil {
		s.BeginCharging = *change.BeginCharging
	}
	if change.LastCharged != nil {
		s.LastCharged = *change.LastCharged
	}
	if change.LastNotified != nil {
		s.LastNotified = *change.LastNotified
	}
	if change.InLockout != nil {
		s.InLockout = *change.InLockout
	}
}

// ByLastChargeDate allows us to sort a []Subscription by the LastCharged
// property, with the lowest LastCharged date first.
type ByLastChargeDate []Subscription

// Len returns the length the SubscriptionsByLastChargeDate. It fulfills
// the sort.Interface interface.
func (s ByLastChargeDate) Len() int {
	return len(s)
}

// Swap puts the item in position i in position j, and the item in position
// j in position i. It fulfills the sort.Interface interface.
func (s ByLastChargeDate) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

// Less returns true if the item in position i should be sorted before the
// item in position j.
func (s ByLastChargeDate) Less(i, j int) bool {
	return s[i].LastCharged.Before(s[j].LastCharged)
}

type subscriptionStore interface {
	reset() error
	createSubscription(sub Subscription) error
	updateSubscription(id uuid.ID, change SubscriptionChange) error
	deleteSubscription(id uuid.ID) error
	listSubscriptionsLastChargedBefore(time.Time) ([]Subscription, error)
	getSubscriptions(ids []uuid.ID) (map[string]Subscription, error)
	getSubscriptionByUser(id uuid.ID) (Subscription, error)
}
