package subscriptions

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

func stripeSubscriptionInMemstore(stripeSubscription string, m *Memstore) bool {
	for _, sub := range m.subscriptions {
		if sub.StripeSubscription == stripeSubscription {
			return true
		}
	}
	return false
}

// CreateSubscription stores the passed Subscription in the Memstore. If
// a Subscription sharing the same UserID already exists, an
// ErrSubscriptionAlreadyExists error will be returned. If a Subscription
// sharing the same StripeSubscription already exists, an
// ErrStripeSubscriptionAlreadyExists error will be returned.
func (m *Memstore) CreateSubscription(sub Subscription) error {
	m.subscriptionLock.Lock()
	defer m.subscriptionLock.Unlock()

	if _, ok := m.subscriptions[sub.UserID.String()]; ok {
		return ErrSubscriptionAlreadyExists
	}
	if stripeSubscriptionInMemstore(sub.StripeSubscription, m) {
		return ErrStripeSubscriptionAlreadyExists
	}
	m.subscriptions[sub.UserID.String()] = sub
	return nil
}

// UpdateSubscription applies the SubscriptionChange passed to the Subscription
// stored in the Memstore associated with the passed ID. If change is empty,
// an ErrSubscriptionChangeEmpty error is returned. If no Subscription is found
// in the Memstore with the passed ID, an ErrSubscriptionNotFound error is returned.
// If change is updating the StripeSubscription, and a Subscription in the Memstore
// already has that value set for StripeSubscription, an
// ErrStripeSubscriptionAlreadyExists error is returned.
func (m *Memstore) UpdateSubscription(id uuid.ID, change SubscriptionChange) error {
	if change.IsEmpty() {
		return ErrSubscriptionChangeEmpty
	}

	m.subscriptionLock.Lock()
	defer m.subscriptionLock.Unlock()

	s, ok := m.subscriptions[id.String()]
	if !ok {
		return ErrSubscriptionNotFound
	}
	if change.StripeSubscription != nil {
		if stripeSubscriptionInMemstore(*change.StripeSubscription, m) {
			return ErrStripeSubscriptionAlreadyExists
		}
	}
	s.ApplyChange(change)
	m.subscriptions[id.String()] = s
	return nil
}

// DeleteSubscription removes the Subscription stored in the Memstore associated
// with the passed ID from the Memstore. If no Subscription is found
// in the Memstore with the passed ID, an ErrSubscriptionNotFound error is returned.
func (m *Memstore) DeleteSubscription(id uuid.ID) error {
	m.subscriptionLock.Lock()
	defer m.subscriptionLock.Unlock()

	_, ok := m.subscriptions[id.String()]
	if !ok {
		return ErrSubscriptionNotFound
	}
	delete(m.subscriptions, id.String())
	return nil
}

// GetSubscriptions retrieves the Subscriptions stored in the Memstore associated
// with the passed IDs. If no IDs are passed, an ErrNoSubscriptionID error is
// returned. No matter how many of the IDs are found (including none), a map is
// returned, with the key being a String()ed version of the ID for the Subscription in
// the value. If no error is returned, the map will represent all of the Subscriptions// matching the passed IDs that exist in the Memstore, even if it's empty.
func (m *Memstore) GetSubscriptions(ids []uuid.ID) (map[string]Subscription, error) {
	if len(ids) < 1 {
		return map[string]Subscription{}, ErrNoSubscriptionID
	}
	m.subscriptionLock.RLock()
	defer m.subscriptionLock.RUnlock()

	result := map[string]Subscription{}

	for _, id := range ids {
		s, ok := m.subscriptions[id.String()]
		if !ok {
			continue
		}
		result[s.UserID.String()] = s
	}
	return result, nil
}

// GetSubscriptionStats returns statistics about the subscription data stored in the
// Memstore as a SubscriptionStats variable. The number of Subscriptions, the
// breakdown of how many Subscriptions belong to each plan, the number of
// Subscriptions that are canceling, and the number of Subscriptions whose payment
// information is failing are all tracked.
func (m *Memstore) GetSubscriptionStats() (SubscriptionStats, error) {
	m.subscriptionLock.RLock()
	defer m.subscriptionLock.RUnlock()

	stats := SubscriptionStats{
		Plans: map[string]int64{},
	}

	for _, subscription := range m.subscriptions {
		stats.Number++
		stats.Plans[subscription.Plan]++

		if subscription.Canceling {
			stats.Canceling++
		}
		if subscription.Status == "past_due" || subscription.Status == "unpaid" {
			stats.Failing++
		}
	}
	return stats, nil
}
