package support

import (
	"fmt"
	"strconv"

	"github.com/mxpv/patreon-go"
	"github.com/pkg/errors"

	"github.com/mxpv/podsync/pkg/api"
	"github.com/mxpv/podsync/pkg/model"

	log "github.com/sirupsen/logrus"
)

const (
	creatorID = "2822191"
)

type storage interface {
	AddPledge(pledge *model.Pledge) error
	UpdatePledge(patronID string, pledge *model.Pledge) error
	DeletePledge(pledge *model.Pledge) error
	GetPledge(patronID string) (*model.Pledge, error)
}

type Patreon struct {
	db storage
}

func ToModel(pledge *patreon.Pledge) (*model.Pledge, error) {
	pledgeID, err := strconv.ParseInt(pledge.ID, 10, 64)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to parse pledge id: %s", pledge.ID)
	}

	patronID, err := strconv.ParseInt(pledge.Relationships.Patron.Data.ID, 10, 64)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to parse patron id: %s", pledge.Relationships.Patron.Data.ID)
	}

	m := &model.Pledge{
		PledgeID:    pledgeID,
		PatronID:    patronID,
		AmountCents: pledge.Attributes.AmountCents,
	}

	if pledge.Attributes.CreatedAt.Valid {
		m.CreatedAt = pledge.Attributes.CreatedAt.Time
	}

	if pledge.Attributes.DeclinedSince.Valid {
		m.DeclinedSince = pledge.Attributes.DeclinedSince.Time
	}

	// Read optional fields

	if pledge.Attributes.TotalHistoricalAmountCents != nil {
		m.TotalHistoricalAmountCents = *pledge.Attributes.TotalHistoricalAmountCents
	}

	if pledge.Attributes.OutstandingPaymentAmountCents != nil {
		m.OutstandingPaymentAmountCents = *pledge.Attributes.OutstandingPaymentAmountCents
	}

	if pledge.Attributes.IsPaused != nil {
		m.IsPaused = *pledge.Attributes.IsPaused
	}

	return m, nil
}

func (h Patreon) Hook(pledge *patreon.Pledge, event string) error {
	logger := log.WithFields(log.Fields{
		"module":       "hook",
		"pledge_id":    pledge.ID,
		"pledge_event": event,
	})

	obj, err := ToModel(pledge)
	if err != nil {
		logger.WithError(err).Error("failed to convert pledge to model")
		return err
	}

	switch event {
	case patreon.EventCreatePledge:
		return h.db.AddPledge(obj)
	case patreon.EventUpdatePledge:
		// Update comes with different PledgeID from Patreon, so do update by user ID
		patronID := pledge.Relationships.Patron.Data.ID
		if err := h.db.UpdatePledge(patronID, obj); err != nil {
			return err
		}

		return nil
	case patreon.EventDeletePledge:
		return h.db.DeletePledge(obj)
	default:
		return fmt.Errorf("unknown event: %s", event)
	}
}

func (h Patreon) FindPledge(patronID string) (*model.Pledge, error) {
	return h.db.GetPledge(patronID)
}

func (h Patreon) GetFeatureLevelByID(patronID string) (level int) {
	level = api.DefaultFeatures

	if patronID == "" {
		return
	}

	if patronID == creatorID {
		level = api.PodcasterFeature
		return
	}

	pledge, err := h.FindPledge(patronID)
	if err != nil {
		log.WithError(err).WithField("user_id", patronID).Error("can't find pledge for user")
		return
	}

	// Check pledge is valid
	if pledge.DeclinedSince.IsZero() && !pledge.IsPaused {
		level = h.GetFeatureLevelFromAmount(pledge.AmountCents)
		return
	}

	return
}

func (h Patreon) GetFeatureLevelFromAmount(amount int) int {
	// Check the amount of pledge
	if amount >= 300 {
		return api.ExtendedPagination
	}

	if amount >= 100 {
		return api.ExtendedFeatures
	}

	return api.DefaultFeatures
}

func NewPatreon(db storage) *Patreon {
	return &Patreon{db: db}
}