package subscriptions

import (
	"log"
	"time"

	"code.secondbit.org/uuid.hg"

	"github.com/stripe/stripe-go"
	"github.com/stripe/stripe-go/customer"
	"github.com/stripe/stripe-go/sub"
)

type Stripe struct {
	apiKey        string
	customers     customer.Client
	subscriptions sub.Client
}

func NewStripe(apiKey string, backend stripe.Backend) Stripe {
	return Stripe{
		apiKey: apiKey,
		customers: customer.Client{
			B:   backend,
			Key: apiKey,
		},
		subscriptions: sub.Client{
			B:   backend,
			Key: apiKey,
		},
	}
}

func CreateStripeCustomer(token, email string, userID uuid.ID, s Stripe) (*stripe.Customer, error) {
	customerParams := &stripe.CustomerParams{
		Desc:  "Customer for user " + userID.String(),
		Email: email,
	}
	customerParams.AddMeta("UserID", userID.String())
	customerParams.SetSource(token)
	c, err := s.customers.New(customerParams)
	if err != nil {
		return nil, err
	}
	return c, nil
}

func CreateStripeSubscription(customer, plan string, userID uuid.ID, s Stripe) (*stripe.Sub, error) {
	subParams := &stripe.SubParams{
		Plan:     plan,
		Customer: customer,
	}
	subParams.AddMeta("UserID", userID.String())

	resp, err := s.subscriptions.New(subParams)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func New(req SubscriptionRequest, s Stripe, store SubscriptionStore) (Subscription, error) {
	subscription := SubscriptionFromRequest(req)
	// create the subscription in our datastore
	// this will fail if they already have a subscription, which prevents duplicate/orphaned Stripe customers being created
	err := store.CreateSubscription(subscription)
	if err != nil {
		return subscription, err
	}

	// create the customer in Stripe, storing the token for reuse
	customer, err := CreateStripeCustomer(req.StripeToken, req.Email, req.UserID, s)
	if err != nil {
		// TODO: delete subscription object
		return subscription, err
	}

	// create the subscription in Stripe, storing the ID for tracking and associating purposes
	stripeSub, err := CreateStripeSubscription(customer.ID, subscription.Plan, subscription.UserID, s)
	if err != nil {
		// TODO: delete customer
		// TODO: delete subscription object
		return subscription, err
	}

	// update our subscription in the datastore with the latest information from Stripe
	change := StripeSubscriptionChange(subscription, *stripeSub)
	err = store.UpdateSubscription(subscription.UserID, change)
	if err != nil {
		log.Printf("Error pairing Stripe subscription %s to user %s: %+v\nUser needs to have their subscription updated manually.", change.StripeSubscription, req.UserID, err)
		return subscription, nil
	}
	subscription.ApplyChange(change)
	return subscription, nil
}

func StripeSubscriptionChange(orig Subscription, subscription stripe.Sub) SubscriptionChange {
	var change SubscriptionChange
	if subscription.Plan != nil && orig.Plan != subscription.Plan.ID {
		change.Plan = &subscription.Plan.ID
	}
	if string(subscription.Status) != orig.Status {
		status := string(subscription.Status)
		change.Status = &status
	}
	if subscription.EndCancel != orig.Canceling {
		change.Canceling = &subscription.EndCancel
	}
	if !time.Unix(subscription.TrialStart, 0).Equal(orig.TrialStart) && !(subscription.TrialStart == 0 && orig.TrialStart.IsZero()) {
		trialStart := time.Unix(subscription.TrialStart, 0)
		change.TrialStart = &trialStart
	}
	if !time.Unix(subscription.TrialEnd, 0).Equal(orig.TrialEnd) && !(subscription.TrialEnd == 0 && orig.TrialEnd.IsZero()) {
		trialEnd := time.Unix(subscription.TrialEnd, 0)
		change.TrialEnd = &trialEnd
	}
	if !time.Unix(subscription.PeriodStart, 0).Equal(orig.PeriodStart) && !(subscription.PeriodStart == 0 && orig.PeriodStart.IsZero()) {
		periodStart := time.Unix(subscription.PeriodStart, 0)
		change.PeriodStart = &periodStart
	}
	if !time.Unix(subscription.PeriodEnd, 0).Equal(orig.PeriodEnd) && !(subscription.PeriodEnd == 0 && orig.PeriodEnd.IsZero()) {
		periodEnd := time.Unix(subscription.PeriodEnd, 0)
		change.PeriodEnd = &periodEnd
	}
	if !time.Unix(subscription.Canceled, 0).Equal(orig.CanceledAt) && !(subscription.Canceled == 0 && orig.CanceledAt.IsZero()) {
		canceledAt := time.Unix(subscription.Canceled, 0)
		change.CanceledAt = &canceledAt
	}
	return change
}
