//go:build !noserver

package cmd

import (
	"errors"
	"fmt"
	"github.com/urfave/cli/v2"
	"heckel.io/ntfy/user"
	"heckel.io/ntfy/util"
	"time"
)

func init() {
	commands = append(commands, cmdTier)
}

const (
	defaultMessageLimit             = 5000
	defaultMessageExpiryDuration    = 12 * time.Hour
	defaultEmailLimit               = 20
	defaultReservationLimit         = 3
	defaultAttachmentFileSizeLimit  = "15M"
	defaultAttachmentTotalSizeLimit = "100M"
	defaultAttachmentExpiryDuration = 6 * time.Hour
	defaultAttachmentBandwidthLimit = "1G"
)

var (
	flagsTier = append([]cli.Flag{}, flagsUser...)
)

var cmdTier = &cli.Command{
	Name:      "tier",
	Usage:     "Manage/show tiers",
	UsageText: "ntfy tier [list|add|remove] ...",
	Flags:     flagsTier,
	Before:    initConfigFileInputSourceFunc("config", flagsUser, initLogFunc),
	Category:  categoryServer,
	Subcommands: []*cli.Command{
		{
			Name:      "add",
			Aliases:   []string{"a"},
			Usage:     "Adds a new tier",
			UsageText: "ntfy tier add [OPTIONS] CODE",
			Action:    execTierAdd,
			Flags: []cli.Flag{
				&cli.StringFlag{Name: "name", Usage: "tier name"},
				&cli.Int64Flag{Name: "message-limit", Value: defaultMessageLimit, Usage: "daily message limit"},
				&cli.DurationFlag{Name: "message-expiry-duration", Value: defaultMessageExpiryDuration, Usage: "duration after which messages are deleted"},
				&cli.Int64Flag{Name: "email-limit", Value: defaultEmailLimit, Usage: "daily email limit"},
				&cli.Int64Flag{Name: "reservation-limit", Value: defaultReservationLimit, Usage: "topic reservation limit"},
				&cli.StringFlag{Name: "attachment-file-size-limit", Value: defaultAttachmentFileSizeLimit, Usage: "per-attachment file size limit"},
				&cli.StringFlag{Name: "attachment-total-size-limit", Value: defaultAttachmentTotalSizeLimit, Usage: "total size limit of attachments for the user"},
				&cli.DurationFlag{Name: "attachment-expiry-duration", Value: defaultAttachmentExpiryDuration, Usage: "duration after which attachments are deleted"},
				&cli.StringFlag{Name: "attachment-bandwidth-limit", Value: defaultAttachmentBandwidthLimit, Usage: "daily bandwidth limit for attachment uploads/downloads"},
				&cli.StringFlag{Name: "stripe-price-id", Usage: "Stripe price ID for paid tiers (e.g. price_12345)"},
			},
			Description: `
FIXME
`,
		},
		{
			Name:      "change",
			Aliases:   []string{"ch"},
			Usage:     "Change a tier",
			UsageText: "ntfy tier change [OPTIONS] CODE",
			Action:    execTierChange,
			Flags: []cli.Flag{
				&cli.StringFlag{Name: "name", Usage: "tier name"},
				&cli.Int64Flag{Name: "message-limit", Usage: "daily message limit"},
				&cli.DurationFlag{Name: "message-expiry-duration", Usage: "duration after which messages are deleted"},
				&cli.Int64Flag{Name: "email-limit", Usage: "daily email limit"},
				&cli.Int64Flag{Name: "reservation-limit", Usage: "topic reservation limit"},
				&cli.StringFlag{Name: "attachment-file-size-limit", Usage: "per-attachment file size limit"},
				&cli.StringFlag{Name: "attachment-total-size-limit", Usage: "total size limit of attachments for the user"},
				&cli.DurationFlag{Name: "attachment-expiry-duration", Usage: "duration after which attachments are deleted"},
				&cli.StringFlag{Name: "attachment-bandwidth-limit", Usage: "daily bandwidth limit for attachment uploads/downloads"},
				&cli.StringFlag{Name: "stripe-price-id", Usage: "Stripe price ID for paid tiers (e.g. price_12345)"},
			},
			Description: `
FIXME
`,
		},
		{
			Name:      "remove",
			Aliases:   []string{"del", "rm"},
			Usage:     "Removes a tier",
			UsageText: "ntfy tier remove CODE",
			Action:    execTierDel,
			Description: `
FIXME
`,
		},
		{
			Name:    "list",
			Aliases: []string{"l"},
			Usage:   "Shows a list of tiers",
			Action:  execTierList,
			Description: `
FIXME
`,
		},
	},
	Description: `Manage tier of the ntfy server.

The command allows you to add/remove/change tier in the ntfy user database. Tiers are used
to grant users higher limits based on their tier.

This is a server-only command. It directly manages the user.db as defined in the server config
file server.yml. The command only works if 'auth-file' is properly defined. Please also refer
to the related command 'ntfy access'.

FIXME

`,
}

func execTierAdd(c *cli.Context) error {
	code := c.Args().Get(0)
	if code == "" {
		return errors.New("tier code expected, type 'ntfy tier add --help' for help")
	} else if !user.AllowedTier(code) {
		return errors.New("tier code must consist only of numbers and letters")
	}
	manager, err := createUserManager(c)
	if err != nil {
		return err
	}
	if tier, _ := manager.Tier(code); tier != nil {
		return fmt.Errorf("tier %s already exists", code)
	}
	name := c.String("name")
	if name == "" {
		name = code
	}
	attachmentFileSizeLimit, err := util.ParseSize(c.String("attachment-file-size-limit"))
	if err != nil {
		return err
	}
	attachmentTotalSizeLimit, err := util.ParseSize(c.String("attachment-total-size-limit"))
	if err != nil {
		return err
	}
	attachmentBandwidthLimit, err := util.ParseSize(c.String("attachment-bandwidth-limit"))
	if err != nil {
		return err
	}
	tier := &user.Tier{
		ID:                       "", // Generated
		Code:                     code,
		Name:                     name,
		MessageLimit:             c.Int64("message-limit"),
		MessageExpiryDuration:    c.Duration("message-expiry-duration"),
		EmailLimit:               c.Int64("email-limit"),
		ReservationLimit:         c.Int64("reservation-limit"),
		AttachmentFileSizeLimit:  attachmentFileSizeLimit,
		AttachmentTotalSizeLimit: attachmentTotalSizeLimit,
		AttachmentExpiryDuration: c.Duration("attachment-expiry-duration"),
		AttachmentBandwidthLimit: attachmentBandwidthLimit,
		StripePriceID:            c.String("stripe-price-id"),
	}
	if err := manager.AddTier(tier); err != nil {
		return err
	}
	tier, err = manager.Tier(code)
	if err != nil {
		return err
	}
	fmt.Fprintf(c.App.ErrWriter, "tier added\n\n")
	printTier(c, tier)
	return nil
}

func execTierChange(c *cli.Context) error {
	code := c.Args().Get(0)
	if code == "" {
		return errors.New("tier code expected, type 'ntfy tier change --help' for help")
	} else if !user.AllowedTier(code) {
		return errors.New("tier code must consist only of numbers and letters")
	}
	manager, err := createUserManager(c)
	if err != nil {
		return err
	}
	tier, err := manager.Tier(code)
	if err == user.ErrTierNotFound {
		return fmt.Errorf("tier %s does not exist", code)
	} else if err != nil {
		return err
	}
	if c.IsSet("name") {
		tier.Name = c.String("name")
	}
	if c.IsSet("message-limit") {
		tier.MessageLimit = c.Int64("message-limit")
	}
	if c.IsSet("message-expiry-duration") {
		tier.MessageExpiryDuration = c.Duration("message-expiry-duration")
	}
	if c.IsSet("email-limit") {
		tier.EmailLimit = c.Int64("email-limit")
	}
	if c.IsSet("reservation-limit") {
		tier.ReservationLimit = c.Int64("reservation-limit")
	}
	if c.IsSet("attachment-file-size-limit") {
		tier.AttachmentFileSizeLimit, err = util.ParseSize(c.String("attachment-file-size-limit"))
		if err != nil {
			return err
		}
	}
	if c.IsSet("attachment-total-size-limit") {
		tier.AttachmentTotalSizeLimit, err = util.ParseSize(c.String("attachment-total-size-limit"))
		if err != nil {
			return err
		}
	}
	if c.IsSet("attachment-expiry-duration") {
		tier.AttachmentExpiryDuration = c.Duration("attachment-expiry-duration")
	}
	if c.IsSet("attachment-bandwidth-limit") {
		tier.AttachmentBandwidthLimit, err = util.ParseSize(c.String("attachment-bandwidth-limit"))
		if err != nil {
			return err
		}
	}
	if c.IsSet("stripe-price-id") {
		tier.StripePriceID = c.String("stripe-price-id")
	}
	if err := manager.UpdateTier(tier); err != nil {
		return err
	}
	fmt.Fprintf(c.App.ErrWriter, "tier updated\n\n")
	printTier(c, tier)
	return nil
}

func execTierDel(c *cli.Context) error {
	code := c.Args().Get(0)
	if code == "" {
		return errors.New("tier code expected, type 'ntfy tier del --help' for help")
	}
	manager, err := createUserManager(c)
	if err != nil {
		return err
	}
	if _, err := manager.Tier(code); err == user.ErrTierNotFound {
		return fmt.Errorf("tier %s does not exist", code)
	}
	if err := manager.RemoveTier(code); err != nil {
		return err
	}
	fmt.Fprintf(c.App.ErrWriter, "tier %s removed\n", code)
	return nil
}

func execTierList(c *cli.Context) error {
	manager, err := createUserManager(c)
	if err != nil {
		return err
	}
	tiers, err := manager.Tiers()
	if err != nil {
		return err
	}
	for _, tier := range tiers {
		printTier(c, tier)
	}
	return nil
}

func printTier(c *cli.Context, tier *user.Tier) {
	stripePriceID := tier.StripePriceID
	if stripePriceID == "" {
		stripePriceID = "(none)"
	}
	fmt.Fprintf(c.App.ErrWriter, "tier %s (id: %s)\n", tier.Code, tier.ID)
	fmt.Fprintf(c.App.ErrWriter, "- Name: %s\n", tier.Name)
	fmt.Fprintf(c.App.ErrWriter, "- Message limit: %d\n", tier.MessageLimit)
	fmt.Fprintf(c.App.ErrWriter, "- Message expiry duration: %s (%d seconds)\n", tier.MessageExpiryDuration.String(), int64(tier.MessageExpiryDuration.Seconds()))
	fmt.Fprintf(c.App.ErrWriter, "- Email limit: %d\n", tier.EmailLimit)
	fmt.Fprintf(c.App.ErrWriter, "- Reservation limit: %d\n", tier.ReservationLimit)
	fmt.Fprintf(c.App.ErrWriter, "- Attachment file size limit: %s\n", util.FormatSize(tier.AttachmentFileSizeLimit))
	fmt.Fprintf(c.App.ErrWriter, "- Attachment total size limit: %s\n", util.FormatSize(tier.AttachmentTotalSizeLimit))
	fmt.Fprintf(c.App.ErrWriter, "- Attachment expiry duration: %s (%d seconds)\n", tier.AttachmentExpiryDuration.String(), int64(tier.AttachmentExpiryDuration.Seconds()))
	fmt.Fprintf(c.App.ErrWriter, "- Attachment daily bandwidth limit: %s\n", util.FormatSize(tier.AttachmentBandwidthLimit))
	fmt.Fprintf(c.App.ErrWriter, "- Stripe price: %s\n", stripePriceID)
}