package callbacksimp

import (
	"context"
	"errors"
	"net/http"
	"net/url"
	"strings"

	"github.com/tech/sendico/pkg/api/http/response"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/server/interface/api/sresponse"
	mutil "github.com/tech/sendico/server/internal/mutil/param"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.uber.org/zap"
)

type signingSecretMutation struct {
	SetSecretRef string
	Clear        bool
	Generated    string
}

func (a *CallbacksAPI) rotateSecret(r *http.Request, account *model.Account, accessToken *sresponse.TokenData) http.HandlerFunc {
	callbackRef, err := a.Cph.GetRef(r)
	if err != nil {
		a.Logger.Warn("Failed to parse callback reference", zap.Error(err), mutil.PLog(a.Cph, r))
		return response.BadReference(a.Logger, a.Name(), a.Cph.Name(), a.Cph.GetID(r), err)
	}

	var callback model.Callback
	if err := a.db.Get(r.Context(), *account.GetID(), callbackRef, &callback); err != nil {
		a.Logger.Warn("Failed to fetch callback for secret rotation", zap.Error(err))
		return response.Auto(a.Logger, a.Name(), err)
	}

	if callback.RetryPolicy.SigningMode != model.CallbackSigningModeHMACSHA256 {
		return response.BadRequest(a.Logger, a.Name(), "invalid_signing_mode", "rotate-secret is available only for hmac_sha256 callbacks")
	}

	secretRef, generatedSecret, err := a.secrets.Provision(r.Context(), callback.OrganizationRef, callbackRef)
	if err != nil {
		a.Logger.Warn("Failed to rotate callback signing secret", zap.Error(err))
		return response.Auto(a.Logger, a.Name(), err)
	}
	if err := a.db.SetSigningSecretRef(r.Context(), *account.GetID(), callbackRef, secretRef); err != nil {
		a.Logger.Warn("Failed to persist rotated callback signing secret reference", zap.Error(err))
		return response.Auto(a.Logger, a.Name(), err)
	}

	return a.callbackResponse(&callback, accessToken, generatedSecret, false)
}

func (a *CallbacksAPI) normalizeAndPrepare(
	ctx context.Context,
	callback *model.Callback,
	organizationRef bson.ObjectID,
	existingSecretRef string,
	allowSecretGeneration bool,
) (signingSecretMutation, error) {
	if callback == nil {
		return signingSecretMutation{}, merrors.InvalidArgument("callback payload is required")
	}
	if organizationRef.IsZero() {
		return signingSecretMutation{}, merrors.InvalidArgument("organization reference is required", "organizationRef")
	}

	callback.Name = strings.TrimSpace(callback.Name)
	callback.Description = trimDescription(callback.Description)

	callback.URL = strings.TrimSpace(callback.URL)
	if callback.URL == "" {
		return signingSecretMutation{}, merrors.InvalidArgument("url is required", "url")
	}
	if err := validateCallbackURL(callback.URL); err != nil {
		return signingSecretMutation{}, err
	}
	if callback.Name == "" {
		callback.Name = callback.URL
	}

	status, err := normalizeStatus(callback.Status, a.config.DefaultStatus)
	if err != nil {
		return signingSecretMutation{}, err
	}
	callback.Status = status
	callback.EventTypes = normalizeEventTypes(callback.EventTypes, a.config.DefaultEventTypes)

	callback.RetryPolicy.Backoff.MinDelayMS = defaultInt(callback.RetryPolicy.Backoff.MinDelayMS, defaultRetryMinDelayMS)
	callback.RetryPolicy.Backoff.MaxDelayMS = defaultInt(callback.RetryPolicy.Backoff.MaxDelayMS, defaultRetryMaxDelayMS)
	if callback.RetryPolicy.Backoff.MaxDelayMS < callback.RetryPolicy.Backoff.MinDelayMS {
		callback.RetryPolicy.Backoff.MaxDelayMS = callback.RetryPolicy.Backoff.MinDelayMS
	}
	callback.RetryPolicy.MaxAttempts = defaultInt(callback.RetryPolicy.MaxAttempts, defaultRetryMaxAttempts)
	callback.RetryPolicy.RequestTimeoutMS = defaultInt(callback.RetryPolicy.RequestTimeoutMS, defaultRetryRequestTimeoutMS)
	callback.RetryPolicy.Headers = normalizeHeaders(callback.RetryPolicy.Headers)

	mode, err := normalizeSigningMode(callback.RetryPolicy.SigningMode)
	if err != nil {
		return signingSecretMutation{}, err
	}
	callback.RetryPolicy.SigningMode = mode

	existingSecretRef = strings.TrimSpace(existingSecretRef)
	switch callback.RetryPolicy.SigningMode {
	case model.CallbackSigningModeNone:
		return signingSecretMutation{Clear: existingSecretRef != ""}, nil
	case model.CallbackSigningModeHMACSHA256:
		if existingSecretRef != "" {
			return signingSecretMutation{SetSecretRef: existingSecretRef}, nil
		}
		if !allowSecretGeneration {
			return signingSecretMutation{}, merrors.InvalidArgument("signing secret is required for hmac_sha256 callbacks", "retryPolicy.signingMode")
		}
		if callback.GetID().IsZero() {
			callback.SetID(bson.NewObjectID())
		}
		secretRef, generatedSecret, err := a.secrets.Provision(ctx, organizationRef, *callback.GetID())
		if err != nil {
			return signingSecretMutation{}, err
		}
		return signingSecretMutation{SetSecretRef: secretRef, Generated: generatedSecret}, nil
	default:
		return signingSecretMutation{}, merrors.InvalidArgument("unsupported signing mode", "retryPolicy.signingMode")
	}
}

func (a *CallbacksAPI) applySigningSecretMutation(
	ctx context.Context,
	accountRef,
	callbackRef bson.ObjectID,
	mutation signingSecretMutation,
) error {
	if callbackRef.IsZero() {
		return merrors.InvalidArgument("callback reference is required", "callbackRef")
	}
	if strings.TrimSpace(mutation.SetSecretRef) != "" {
		return a.db.SetSigningSecretRef(ctx, accountRef, callbackRef, mutation.SetSecretRef)
	}
	if mutation.Clear {
		err := a.db.ClearSigningSecretRef(ctx, accountRef, callbackRef)
		if err != nil && !errors.Is(err, merrors.ErrNoData) {
			return err
		}
	}
	return nil
}

func (a *CallbacksAPI) callbackResponse(
	callback *model.Callback,
	accessToken *sresponse.TokenData,
	generatedSecret string,
	created bool,
) http.HandlerFunc {
	if callback == nil || accessToken == nil {
		return response.Internal(a.Logger, a.Name(), merrors.Internal("failed to build callback response"))
	}

	return sresponse.Callback(a.Logger, callback, accessToken, generatedSecret, created)
}

func normalizeStatus(raw, fallback model.CallbackStatus) (model.CallbackStatus, error) {
	candidate := strings.ToLower(strings.TrimSpace(string(raw)))
	if candidate == "" {
		candidate = strings.ToLower(strings.TrimSpace(string(fallback)))
	}

	switch candidate {
	case "", "active", "enabled":
		return model.CallbackStatusActive, nil
	case "disabled", "inactive":
		return model.CallbackStatusDisabled, nil
	default:
		return "", merrors.InvalidArgument("unsupported callback status", "status")
	}
}

func normalizeSigningMode(raw model.CallbackSigningMode) (model.CallbackSigningMode, error) {
	mode := strings.ToLower(strings.TrimSpace(string(raw)))
	switch mode {
	case "", "none":
		return model.CallbackSigningModeNone, nil
	case "hmac_sha256", "hmac-sha256", "hmac":
		return model.CallbackSigningModeHMACSHA256, nil
	default:
		return "", merrors.InvalidArgument("unsupported callback signing mode", "retryPolicy.signingMode")
	}
}

func normalizeEventTypes(eventTypes []string, defaults []string) []string {
	if len(eventTypes) == 0 {
		return normalizeEventTypes(defaults, nil)
	}
	seen := make(map[string]struct{}, len(eventTypes))
	out := make([]string, 0, len(eventTypes))
	for _, eventType := range eventTypes {
		value := strings.TrimSpace(eventType)
		if value == "" {
			continue
		}
		if _, exists := seen[value]; exists {
			continue
		}
		seen[value] = struct{}{}
		out = append(out, value)
	}
	if len(out) == 0 {
		if len(defaults) > 0 {
			return normalizeEventTypes(defaults, nil)
		}
		return []string{model.PaymentStatusUpdatedType}
	}
	return out
}

func normalizeHeaders(headers map[string]string) map[string]string {
	if len(headers) == 0 {
		return nil
	}
	out := make(map[string]string, len(headers))
	for key, value := range headers {
		k := strings.TrimSpace(key)
		if k == "" {
			continue
		}
		out[k] = strings.TrimSpace(value)
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

func mergeCallbackMutable(dst, src *model.Callback) {
	dst.OrganizationRef = src.OrganizationRef
	dst.Describable = src.Describable
	dst.Status = src.Status
	dst.URL = src.URL
	dst.EventTypes = append([]string(nil), src.EventTypes...)
	dst.RetryPolicy = model.CallbackRetryPolicy{
		Backoff: model.CallbackBackoff{
			MinDelayMS: src.RetryPolicy.Backoff.MinDelayMS,
			MaxDelayMS: src.RetryPolicy.Backoff.MaxDelayMS,
		},
		SigningMode:      src.RetryPolicy.SigningMode,
		Headers:          normalizeHeaders(src.RetryPolicy.Headers),
		MaxAttempts:      src.RetryPolicy.MaxAttempts,
		RequestTimeoutMS: src.RetryPolicy.RequestTimeoutMS,
	}
}

func defaultInt(value, fallback int) int {
	if value > 0 {
		return value
	}
	return fallback
}

func trimDescription(in *string) *string {
	if in == nil {
		return nil
	}
	value := strings.TrimSpace(*in)
	if value == "" {
		return nil
	}
	return &value
}

func validateCallbackURL(raw string) error {
	parsed, err := url.ParseRequestURI(raw)
	if err != nil {
		return merrors.InvalidArgument("url is invalid", "url")
	}
	switch strings.ToLower(strings.TrimSpace(parsed.Scheme)) {
	case "https", "http":
	default:
		return merrors.InvalidArgument("url scheme must be http or https", "url")
	}
	if strings.TrimSpace(parsed.Host) == "" {
		return merrors.InvalidArgument("url host is required", "url")
	}
	return nil
}
