package providers

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/niels/banking-app/backend/internal/domain"
)

const (
	SignatureVerified = "verified"
	SignatureInvalid  = "invalid"
	SignatureMissing  = "missing"

	ReplayAccepted  = "accepted"
	ReplayDuplicate = "duplicate"

	OrderingInOrder    = "in_order"
	OrderingNoSequence = "no_sequence"
	OrderingGap        = "gap"
	OrderingRegression = "regression"
)

type WebhookVerification struct {
	ProviderType string
	ProviderName string
	Secret       []byte
	Signature    string
	Timestamp    string
	Body         []byte
	Tolerance    time.Duration
}

type WebhookEventRecord struct {
	ProviderType     string
	ProviderName     string
	ExternalEventID  string
	EventType        string
	SequenceNumber   *int64
	Payload          []byte
	SignatureStatus  string
	ProcessingStatus string
	ReceivedAt       time.Time
}

type WebhookEventDecision struct {
	Inserted       bool   `json:"inserted"`
	ReplayStatus   string `json:"replay_status"`
	OrderingStatus string `json:"ordering_status"`
}

type WebhookRecorder interface {
	RecordWebhookEvent(ctx context.Context, event WebhookEventRecord) (WebhookEventDecision, error)
}

func VerifyWebhookHMAC(params WebhookVerification) error {
	signature := strings.TrimPrefix(strings.TrimSpace(params.Signature), "sha256=")
	if signature == "" {
		return fmt.Errorf("%w: missing provider webhook signature", domain.ErrUnauthorized)
	}
	if len(params.Secret) == 0 {
		return fmt.Errorf("%w: provider webhook secret is not configured", domain.ErrProviderUnavailable)
	}
	got, err := hex.DecodeString(signature)
	if err != nil {
		return fmt.Errorf("%w: invalid provider webhook signature encoding", domain.ErrUnauthorized)
	}

	payloads := [][]byte{params.Body}
	if strings.TrimSpace(params.Timestamp) != "" {
		if err := validateWebhookTimestamp(params.Timestamp, params.Tolerance); err != nil {
			return err
		}
		payloads = append(payloads, []byte(strings.TrimSpace(params.Timestamp)+"."+string(params.Body)))
	}
	for _, payload := range payloads {
		mac := hmac.New(sha256.New, params.Secret)
		_, _ = mac.Write(payload)
		if hmac.Equal(got, mac.Sum(nil)) {
			return nil
		}
	}
	return fmt.Errorf("%w: invalid provider webhook signature", domain.ErrUnauthorized)
}

func validateWebhookTimestamp(raw string, tolerance time.Duration) error {
	if tolerance <= 0 {
		tolerance = 5 * time.Minute
	}
	raw = strings.TrimSpace(raw)
	var eventTime time.Time
	if unix, err := strconv.ParseInt(raw, 10, 64); err == nil {
		eventTime = time.Unix(unix, 0).UTC()
	} else {
		parsed, parseErr := time.Parse(time.RFC3339, raw)
		if parseErr != nil {
			return fmt.Errorf("%w: invalid provider webhook timestamp", domain.ErrUnauthorized)
		}
		eventTime = parsed.UTC()
	}
	now := time.Now().UTC()
	if eventTime.Before(now.Add(-tolerance)) || eventTime.After(now.Add(tolerance)) {
		return fmt.Errorf("%w: stale provider webhook timestamp", domain.ErrUnauthorized)
	}
	return nil
}

func WebhookSequence(raw string) *int64 {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil
	}
	sequence, err := strconv.ParseInt(raw, 10, 64)
	if err != nil || sequence <= 0 {
		return nil
	}
	return &sequence
}
