package api

import (
	"net/http"

	"code.secondbit.org/api.hg"
	"code.secondbit.org/auth.hg"
	"code.secondbit.org/trout.hg"
	"code.secondbit.org/uuid.hg"

	"code.secondbit.org/ducky/subscriptions.hg"

	"golang.org/x/net/context"
)

var (
	ScopeSubscription      = auth.Scope{ID: "subscriptions", Name: "Manage Subscriptions", Description: "Read and update your subscription information."}
	ScopeSubscriptionAdmin = auth.Scope{ID: "subscriptions_admin", Name: "Administer Subscriptions", Description: "Read and update subscription information, bypassing ACL."}
)

// changingSystemProperties takes a SubscriptionChange and returns a
// slice of JSON pointers to the properties that are changing but are
// also designated as "system properties" in the API. If no properties
// meet these criteria, an empty slice is returned.
func changingSystemProperties(change subscriptions.SubscriptionChange) []string {
	var changes []string
	if change.StripeSubscription != nil {
		changes = append(changes, "/stripe_subscription")
	}
	if change.Status != nil {
		changes = append(changes, "/status")
	}
	if change.TrialStart != nil {
		changes = append(changes, "/trial_start")
	}
	if change.TrialEnd != nil {
		changes = append(changes, "/trial_end")
	}
	if change.PeriodStart != nil {
		changes = append(changes, "/period_start")
	}
	if change.PeriodEnd != nil {
		changes = append(changes, "/period_end")
	}
	if change.CanceledAt != nil {
		changes = append(changes, "/canceled_at")
	}
	if change.FailedChargeAttempts != nil {
		changes = append(changes, "/failed_charge_attempts")
	}
	if change.LastFailedCharge != nil {
		changes = append(changes, "/last_failed_charge")
	}
	if change.LastNotified != nil {
		changes = append(changes, "/last_notified")
	}
	return changes
}

func HandleSubscriptions(router *trout.Router, c context.Context) {
	router.Endpoint("/subscriptions").Methods("POST", "OPTIONS").Handler(
		api.CORSMiddleware(api.NegotiateMiddleware(api.ContextWrapper(c, CreateSubscriptionHandler))))
	router.Endpoint("/subscriptions/{id}").Methods("GET", "OPTIONS").Handler(
		api.CORSMiddleware(api.NegotiateMiddleware(api.ContextWrapper(c, GetSubscriptionHandler))))
	router.Endpoint("/subscriptions/{id}").Methods("PATCH", "OPTIONS").Handler(
		api.CORSMiddleware(api.NegotiateMiddleware(api.ContextWrapper(c, PatchSubscriptionHandler))))
}

func CreateSubscriptionHandler(w http.ResponseWriter, r *http.Request, c context.Context) {
	store, err := getSubscriptionStore(c)
	if err != nil {
		api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
		return
	}
	stripe, err := getStripeClient(c)
	if err != nil {
		api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
		return
	}
	if !api.CheckScopes(r, ScopeSubscriptionAdmin.ID) {
		api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
		return
	}
	var req subscriptions.SubscriptionChange
	err = api.Decode(r, &req)
	if err != nil {
		api.Encode(w, r, http.StatusBadRequest, Response{Errors: api.InvalidFormatError})
		return
	}
	// BUG(paddy): Need to validate the request when creating a subscription
	sub, err := subscriptions.New(req, stripe, store)
	if err != nil {
		var rErr api.RequestError
		var code int
		switch err {
		case subscriptions.ErrSubscriptionAlreadyExists:
			rErr = api.RequestError{Slug: api.RequestErrConflict, Field: "/user_id"}
			code = http.StatusBadRequest
		case subscriptions.ErrStripeSubscriptionAlreadyExists:
			rErr = api.RequestError{Slug: api.RequestErrConflict, Field: "/stripe_token"}
			code = http.StatusBadRequest
		default:
			rErr = api.RequestError{Slug: api.RequestErrActOfGod}
			code = http.StatusInternalServerError
		}
		api.Encode(w, r, code, Response{Errors: []api.RequestError{rErr}})
		return
	}
	resp := Response{Subscriptions: []subscriptions.Subscription{sub}}
	api.Encode(w, r, http.StatusCreated, resp)
}

func GetSubscriptionHandler(w http.ResponseWriter, r *http.Request, c context.Context) {
	store, err := getSubscriptionStore(c)
	if err != nil {
		api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
		return
	}
	vars := trout.RequestVars(r)
	rawID := vars.Get("id")
	if rawID == "" {
		api.Encode(w, r, http.StatusBadRequest, Response{Errors: []api.RequestError{{Slug: api.RequestErrMissing, Param: "id"}}})
		return
	}
	id, err := uuid.Parse(rawID)
	if err != nil {
		api.Encode(w, r, http.StatusBadRequest, Response{Errors: []api.RequestError{{Slug: api.RequestErrInvalidFormat, Param: "id"}}})
		return
	}
	if !api.CheckScopes(r, ScopeSubscription.ID) {
		api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
		return
	}
	userID, err := api.AuthUser(r)
	if err != nil {
		api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
		return
	}
	if !id.Equal(userID) && !api.CheckScopes(r, ScopeSubscriptionAdmin.ID) {
		api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
		return
	}
	subs, err := store.GetSubscriptions([]uuid.ID{id})
	if err != nil {
		api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
		return
	}
	sub, ok := subs[id.String()]
	if !ok {
		api.Encode(w, r, http.StatusNotFound, Response{Errors: []api.RequestError{{Slug: api.RequestErrNotFound, Param: "id"}}})
		return
	}
	resp := Response{Subscriptions: []subscriptions.Subscription{sub}}
	api.Encode(w, r, http.StatusOK, resp)
}

func PatchSubscriptionHandler(w http.ResponseWriter, r *http.Request, c context.Context) {
	store, err := getSubscriptionStore(c)
	if err != nil {
		api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
		return
	}
	stripe, err := getStripeClient(c)
	if err != nil {
		api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
		return
	}
	if !api.CheckScopes(r, ScopeSubscription.ID) {
		api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
		return
	}
	userID, err := api.AuthUser(r)
	if err != nil {
		api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
		return
	}
	vars := trout.RequestVars(r)
	rawID := vars.Get("id")
	if rawID == "" {
		api.Encode(w, r, http.StatusBadRequest, Response{Errors: []api.RequestError{{Slug: api.RequestErrMissing, Param: "id"}}})
		return
	}
	id, err := uuid.Parse(rawID)
	if err != nil {
		api.Encode(w, r, http.StatusBadRequest, Response{Errors: []api.RequestError{{Slug: api.RequestErrInvalidFormat, Param: "id"}}})
		return
	}

	var req subscriptions.SubscriptionChange
	err = api.Decode(r, &req)
	if err != nil {
		api.Encode(w, r, http.StatusBadRequest, Response{Errors: api.InvalidFormatError})
		return
	}
	// BUG(paddy): Need to validate the request when updating a subscription

	// only admin users can update the system-controlled properties
	changedSysProps := changingSystemProperties(req)
	if len(changedSysProps) > 0 && !api.CheckScopes(r, ScopeSubscriptionAdmin.ID) {
		errs := make([]api.RequestError, len(changedSysProps))
		for pos, prop := range changedSysProps {
			errs[pos] = api.RequestError{Slug: api.RequestErrAccessDenied, Field: prop}
		}
		api.Encode(w, r, http.StatusBadRequest, Response{Errors: errs})
		return
	}

	subs, err := store.GetSubscriptions([]uuid.ID{userID})
	if err != nil {
		api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
		return
	}
	sub, ok := subs[userID.String()]
	if !ok {
		api.Encode(w, r, http.StatusNotFound, Response{Errors: []api.RequestError{{Slug: api.RequestErrNotFound, Param: "id"}}})
		return
	}
	stripeSub, err := subscriptions.UpdateStripeSubscription(sub.StripeSubscription, req.Plan, req.StripeSource, stripe)
	if err != nil {
		api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
		return
	}
	change := subscriptions.StripeSubscriptionChange(sub, *stripeSub)
	if change.IsEmpty() {
		resp := Response{Subscriptions: []subscriptions.Subscription{sub}}
		api.Encode(w, r, http.StatusOK, resp)
		return
	}
	err = store.UpdateSubscription(id, change)
	if err != nil {
		api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
		return
	}
	sub.ApplyChange(change)
	resp := Response{Subscriptions: []subscriptions.Subscription{sub}}
	api.Encode(w, r, http.StatusOK, resp)
}
