package callbacksimp

import (
	"context"

	api "github.com/tech/sendico/pkg/api/http"
	"github.com/tech/sendico/pkg/db/callbacks"
	"github.com/tech/sendico/pkg/db/transaction"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/mservice"
	eapi "github.com/tech/sendico/server/interface/api"
	"github.com/tech/sendico/server/internal/server/papitemplate"
	"go.uber.org/zap"
)

type CallbacksAPI struct {
	papitemplate.ProtectedAPI[model.Callback]
	db      callbacks.DB
	tf      transaction.Factory
	secrets signingSecretManager
	config  callbacksConfig
}

func (a *CallbacksAPI) Name() mservice.Type {
	return mservice.Callbacks
}

func (a *CallbacksAPI) Finish(_ context.Context) error {
	return nil
}

func CreateAPI(apiCtx eapi.API) (*CallbacksAPI, error) {
	dbFactory := func() (papitemplate.ProtectedDB[model.Callback], error) {
		return apiCtx.DBFactory().NewCallbacksDB()
	}

	res := &CallbacksAPI{
		config: newCallbacksConfig(apiCtx.Config().Callbacks),
		tf:     apiCtx.DBFactory().TransactionFactory(),
	}

	p, err := papitemplate.CreateAPI(apiCtx, dbFactory, mservice.Organizations, mservice.Callbacks)
	if err != nil {
		return nil, err
	}
	res.ProtectedAPI = *p.
		WithNoCreateNotification().
		WithNoUpdateNotification().
		WithNoDeleteNotification().
		WithCreateHandler(res.create).
		WithUpdateHandler(res.update).
		Build()

	if res.db, err = apiCtx.DBFactory().NewCallbacksDB(); err != nil {
		res.Logger.Warn("Failed to create callbacks database", zap.Error(err))
		return nil, err
	}

	if res.secrets, err = newSigningSecretManager(res.Logger, res.config); err != nil {
		res.Logger.Warn("Failed to initialize callbacks signing secret manager", zap.Error(err))
		return nil, err
	}

	apiCtx.Register().AccountHandler(res.Name(), res.Cph.AddRef("/rotate-secret"), api.Post, res.rotateSecret)

	return res, nil
}

const (
	defaultCallbackStatus           = model.CallbackStatusActive
	defaultRetryMaxAttempts         = 8
	defaultRetryMinDelayMS          = 1000
	defaultRetryMaxDelayMS          = 300000
	defaultRetryRequestTimeoutMS    = 10000
	defaultSigningSecretLengthBytes = 32
	defaultSigningSecretField       = "value"
	defaultSigningSecretPathPrefix  = "sendico/callbacks"
)

type callbacksConfig struct {
	DefaultEventTypes []string
	DefaultStatus     model.CallbackStatus
	SecretPathPrefix  string
	SecretField       string
	SecretLengthBytes int
	Vault             VaultConfig
}

type VaultConfig struct {
	Address      string
	TokenEnv     string
	TokenFileEnv string
	TokenFile    string
	Namespace    string
	MountPath    string
}

func newCallbacksConfig(source *eapi.CallbacksConfig) callbacksConfig {
	cfg := callbacksConfig{
		DefaultEventTypes: []string{model.PaymentStatusUpdatedType},
		DefaultStatus:     defaultCallbackStatus,
		SecretPathPrefix:  defaultSigningSecretPathPrefix,
		SecretField:       defaultSigningSecretField,
		SecretLengthBytes: defaultSigningSecretLengthBytes,
	}
	if source == nil {
		return cfg
	}

	if source.SecretPathPrefix != "" {
		cfg.SecretPathPrefix = source.SecretPathPrefix
	}
	if source.SecretField != "" {
		cfg.SecretField = source.SecretField
	}
	if source.SecretLengthBytes > 0 {
		cfg.SecretLengthBytes = source.SecretLengthBytes
	}
	if len(source.DefaultEventTypes) > 0 {
		cfg.DefaultEventTypes = source.DefaultEventTypes
	}
	if source.DefaultStatus != "" {
		cfg.DefaultStatus = model.CallbackStatus(source.DefaultStatus)
	}
	cfg.Vault = VaultConfig{
		Address:      source.Vault.Address,
		TokenEnv:     source.Vault.TokenEnv,
		TokenFileEnv: source.Vault.TokenFileEnv,
		TokenFile:    source.Vault.TokenFile,
		Namespace:    source.Vault.Namespace,
		MountPath:    source.Vault.MountPath,
	}
	return cfg
}

func (c callbacksConfig) validate() error {
	if c.SecretLengthBytes <= 0 {
		return merrors.InvalidArgument("callbacks signing secret length must be greater than zero", "api.callbacks.secret_length_bytes")
	}
	return nil
}
