package webhook

import (
	"sync/atomic"
	"time"

	"github.com/dlclark/regexp2"
	flag "github.com/spf13/pflag"

	"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
	"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
)

func init() {
	gotenberg.MustRegisterModule(new(Webhook))
}

// Webhook is a module that provides a middleware for uploading output files
// to any destinations in an asynchronous fashion.
type Webhook struct {
	enableSyncMode bool
	allowList      []*regexp2.Regexp
	denyList       []*regexp2.Regexp
	errorAllowList []*regexp2.Regexp
	errorDenyList  []*regexp2.Regexp
	denyPrivateIPs bool
	denyPublicIPs  bool
	maxRetry       int
	retryMinWait   time.Duration
	retryMaxWait   time.Duration
	clientTimeout  time.Duration
	asyncCount     atomic.Int64
	disable        bool
}

// Descriptor returns an [Webhook]'s module descriptor.
func (w *Webhook) Descriptor() gotenberg.ModuleDescriptor {
	return gotenberg.ModuleDescriptor{
		ID: "webhook",
		FlagSet: func() *flag.FlagSet {
			fs := flag.NewFlagSet("webhook", flag.ExitOnError)
			fs.Bool("webhook-enable-sync-mode", false, "Enable synchronous mode for the webhook feature")
			fs.StringSlice("webhook-allow-list", []string{}, "Set the allowed URLs for the webhook feature using regular expressions - supports multiple values")
			fs.StringSlice("webhook-deny-list", []string{}, "Set the denied URLs for the webhook feature using regular expressions - supports multiple values")
			fs.Bool("webhook-deny-private-ips", false, "Reject webhook URLs whose host resolves to a non-public IP address (loopback, RFC1918, link-local, unique-local). Enable on deployments that accept untrusted webhook destinations to mitigate SSRF against internal services")
			fs.Bool("webhook-deny-public-ips", false, "Reject webhook URLs whose host resolves to a public IP address. Enable on air-gapped or data-governed deployments to prevent callbacks from leaving a private network")
			fs.Int("webhook-max-retry", 4, "Set the maximum number of retries for the webhook feature")

			// Deprecated flags.
			fs.StringSlice("webhook-error-allow-list", []string{}, "Set the allowed URLs in case of an error for the webhook feature using regular expressions - supports multiple values")
			fs.StringSlice("webhook-error-deny-list", []string{}, "Set the denied URLs in case of an error for the webhook feature using regular expressions - supports multiple values")
			err := fs.MarkDeprecated("webhook-error-allow-list", "use --webhook-allow-list instead")
			if err != nil {
				panic(err)
			}
			err = fs.MarkDeprecated("webhook-error-deny-list", "use --webhook-deny-list instead")
			if err != nil {
				panic(err)
			}
			fs.Duration("webhook-retry-min-wait", time.Duration(1)*time.Second, "Set the minimum duration to wait before trying to call the webhook again")
			fs.Duration("webhook-retry-max-wait", time.Duration(30)*time.Second, "Set the maximum duration to wait before trying to call the webhook again")
			fs.Duration("webhook-client-timeout", time.Duration(30)*time.Second, "Set the time limit for requests to the webhook")
			fs.Bool("webhook-disable", false, "Disable the webhook feature")

			return fs
		}(),
		New: func() gotenberg.Module { return new(Webhook) },
	}
}

// Provision sets the module properties.
func (w *Webhook) Provision(ctx *gotenberg.Context) error {
	flags := ctx.ParsedFlags()
	w.enableSyncMode = flags.MustBool("webhook-enable-sync-mode")
	w.allowList = flags.MustRegexpSlice("webhook-allow-list")
	w.denyList = flags.MustRegexpSlice("webhook-deny-list")
	w.errorAllowList = flags.MustDeprecatedRegexpSlice("webhook-error-allow-list", "webhook-allow-list")
	w.errorDenyList = flags.MustDeprecatedRegexpSlice("webhook-error-deny-list", "webhook-deny-list")
	w.denyPrivateIPs = flags.MustBool("webhook-deny-private-ips")
	w.denyPublicIPs = flags.MustBool("webhook-deny-public-ips")
	w.maxRetry = flags.MustInt("webhook-max-retry")
	w.retryMinWait = flags.MustDuration("webhook-retry-min-wait")
	w.retryMaxWait = flags.MustDuration("webhook-retry-max-wait")
	w.clientTimeout = flags.MustDuration("webhook-client-timeout")
	w.disable = flags.MustBool("webhook-disable")
	w.asyncCount.Store(0)

	return nil
}

// Middlewares returns the middleware.
func (w *Webhook) Middlewares() ([]api.Middleware, error) {
	if w.disable {
		return nil, nil
	}

	return []api.Middleware{
		webhookMiddleware(w),
	}, nil
}

// AsyncCount returns the number of asynchronous requests.
func (w *Webhook) AsyncCount() int64 {
	return w.asyncCount.Load()
}

// Interface guards.
var (
	_ gotenberg.Module        = (*Webhook)(nil)
	_ gotenberg.Provisioner   = (*Webhook)(nil)
	_ api.MiddlewareProvider  = (*Webhook)(nil)
	_ api.AsynchronousCounter = (*Webhook)(nil)
)
