package jobs

import (
	"context"
	"encoding/json"
	"log/slog"
	"time"

	"github.com/yan1h/agent-coding-workflow/internal/audit"
	"github.com/yan1h/agent-coding-workflow/internal/infra/clock"
	"github.com/yan1h/agent-coding-workflow/internal/infra/notify"
)

// WebhookNotifierConfig drives the WebhookNotifier. Backoff defines the retry
// schedule passed to the worker via job MaxAttempts (len(Backoff)+1, since
// the first attempt is "attempt 1" not a retry). Topics is the whitelist of
// topics that get enqueued; everything else is dropped.
type WebhookNotifierConfig struct {
	Enabled bool
	Topics  []string
	Backoff []time.Duration
}

// WebhookNotifier implements notify.Notifier. Its Send method does NOT actually
// deliver — it enqueues a webhook.deliver job. The Worker + WebhookHandler
// performs the real HTTP call. This indirection gives us retry / dead-letter /
// crash safety without coupling notify.Dispatcher to the jobs DB.
type WebhookNotifier struct {
	repo Repository
	cfg  WebhookNotifierConfig
	clk  clock.Clock
	rec  audit.Recorder
	log  *slog.Logger
}

// NewWebhookNotifier constructs the notifier. rec is optional; when non-nil the
// notifier emits a job.enqueue audit row after each successful Enqueue.
func NewWebhookNotifier(repo Repository, cfg WebhookNotifierConfig, clk clock.Clock, rec audit.Recorder, log *slog.Logger) *WebhookNotifier {
	if clk == nil {
		clk = clock.Real()
	}
	if log == nil {
		log = slog.Default()
	}
	return &WebhookNotifier{repo: repo, cfg: cfg, clk: clk, rec: rec, log: log}
}

// Name reports the notify.Channel for this notifier.
func (n *WebhookNotifier) Name() notify.Channel { return notify.ChannelWebhook }

// Send enqueues a webhook.deliver job. Disabled config / unmatched topics are
// silently skipped (returning nil, since the dispatcher's contract treats
// per-channel failures as non-fatal anyway).
func (n *WebhookNotifier) Send(ctx context.Context, msg notify.Message) error {
	if !n.cfg.Enabled {
		return nil
	}
	if !topicAllowed(n.cfg.Topics, msg.Topic) {
		return nil
	}
	payload, err := json.Marshal(map[string]any{
		"topic":       msg.Topic,
		"severity":    string(msg.Severity),
		"title":       msg.Title,
		"body":        msg.Body,
		"link":        msg.Link,
		"metadata":    msg.Metadata,
		"occurred_at": msg.CreatedAt.UTC().Format(time.RFC3339),
	})
	if err != nil {
		return err
	}
	maxAttempts := int32(len(n.cfg.Backoff) + 1)
	if maxAttempts < 1 {
		maxAttempts = 1
	}
	enqueued, err := n.repo.Enqueue(ctx, TypeWebhookDeliver, payload, n.clk.Now(), maxAttempts)
	if err != nil {
		return err
	}
	if n.rec != nil {
		if rerr := n.rec.Record(ctx, audit.Entry{
			Action:     "job.enqueue",
			TargetType: "job",
			TargetID:   enqueued.ID.String(),
			Metadata: map[string]any{
				"topic":    msg.Topic,
				"job_type": string(TypeWebhookDeliver),
			},
		}); rerr != nil {
			n.log.Warn("webhook_notifier.audit_failed", "job_id", enqueued.ID, "err", rerr.Error())
		}
	}
	return nil
}

func topicAllowed(allow []string, t string) bool {
	for _, x := range allow {
		if x == t {
			return true
		}
	}
	return false
}
