package openai

import (
	"context"
	"encoding/hex"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"iop/packages/go/streamgate"
)

const filterObservationLogMessage = "streamgate_filter_observation"

type zapFilterObservationSink struct {
	core zapcore.Core
}

var _ streamgate.ObservationSink = (*zapFilterObservationSink)(nil)

func newZapFilterObservationSink(logger *zap.Logger) streamgate.ObservationSink {
	if logger == nil {
		return &zapFilterObservationSink{
			core: zap.NewNop().Core(),
		}
	}
	return &zapFilterObservationSink{
		core: logger.Core(),
	}
}

func (s *zapFilterObservationSink) Emit(ctx context.Context, obs streamgate.FilterObservation) error {
	if err := obs.Validate(); err != nil {
		return err
	}

	entry := zapcore.Entry{
		Level:   zapcore.InfoLevel,
		Time:    obs.OccurredAt(),
		Message: filterObservationLogMessage,
	}

	if !s.core.Enabled(entry.Level) {
		return nil
	}

	fields := make([]zapcore.Field, 0, 36)

	// Base allowlist
	fields = append(fields,
		zap.Uint64("sequence", obs.Sequence()),
		zap.String("observation_kind", string(obs.Kind())),
		zap.String("correlation_id", obs.StableCorrelation()),
		zap.String("config_generation", obs.ConfigGeneration()),
		zap.String("attempt_id", obs.AttemptID()),
		zap.String("model_group", obs.AttemptTarget().ModelGroup()),
		zap.String("actual_model", obs.AttemptTarget().Model()),
		zap.String("actual_provider", obs.AttemptTarget().Provider()),
		zap.String("execution_path", obs.AttemptTarget().ExecutionPath()),
		zap.Uint64("epoch_id", obs.EpochID()),
		zap.String("commit_state", string(obs.CommitState())),
	)

	// Optional allowlist: attribution
	if attr := obs.Attribution(); attr != nil {
		fields = append(fields,
			zap.String("consumer_id", attr.ConsumerID()),
			zap.String("filter_id", attr.FilterID()),
			zap.String("rule_id", attr.RuleID()),
		)
	}

	// Optional allowlist: hold
	if h := obs.Hold(); h != nil {
		fields = append(fields,
			zap.String("hold_mode", string(h.Mode())),
			zap.String("event_kind", string(h.EventKind())),
			zap.Int("effective_runes", h.EffectiveRunes()),
			zap.Int("pending_runes", h.PendingRunes()),
		)
	}

	// Optional allowlist: decision
	if d := obs.DecisionPolicy(); d != nil {
		fields = append(fields, zap.String("filter_outcome", string(d.Outcome())))
		if dk := d.DecisionKind(); dk != "" {
			fields = append(fields, zap.String("decision_kind", string(dk)))
		}
		fields = append(fields,
			zap.String("enforcement", string(d.Enforcement())),
			zap.String("failure_disposition", string(d.FailureDisposition())),
		)
	}

	// Optional allowlist: arbitration
	if arb := obs.Arbitration(); arb != nil {
		fields = append(fields,
			zap.String("arbitration_action", string(arb.Action())),
			zap.String("base_disposition", string(arb.BaseDisposition())),
		)
	}

	// Optional allowlist: terminal_reason
	if tr := obs.TerminalReason(); tr != "" {
		fields = append(fields, zap.String("terminal_reason", string(tr)))
	}

	// Optional allowlist: recovery
	if rec := obs.Recovery(); rec != nil {
		fields = append(fields,
			zap.String("plan_id", rec.PlanID()),
			zap.String("recovery_strategy", string(rec.Strategy())),
			zap.String("resume_mode", string(rec.ResumeMode())),
		)
		if sharedID := rec.SharedAttemptID(); sharedID != "" {
			fields = append(fields, zap.String("shared_attempt_id", sharedID))
		}
	}

	// Optional allowlist: preparer
	if p := obs.Preparer(); p != nil {
		fields = append(fields,
			zap.String("preparer_id", p.PreparerID()),
			zap.String("preparer_status", string(p.Status())),
			zap.String("preparer_deadline_outcome", string(p.DeadlineOutcome())),
		)
	}

	// Bounded cause chain
	if causes := obs.Causes(); causes.Len() > 0 {
		n := causes.Len()
		stages := make([]string, n)
		codes := make([]string, n)
		consumers := make([]string, n)
		filters := make([]string, n)
		ruleIDs := make([]string, n)

		for i := 0; i < n; i++ {
			c, _ := causes.At(i)
			stages[i] = c.Stage()
			codes[i] = c.Code()
			consumers[i] = c.Consumer()
			filters[i] = c.Filter()
			ruleIDs[i] = c.RuleID()
		}

		fields = append(fields,
			zap.Int("cause_count", n),
			zap.Strings("cause_stages", stages),
			zap.Strings("cause_codes", codes),
			zap.Strings("cause_consumers", consumers),
			zap.Strings("cause_filters", filters),
			zap.Strings("cause_rule_ids", ruleIDs),
		)
	}

	// Optional evidence
	if ev := obs.Evidence(); ev != nil {
		fp := ev.Fingerprint().Bytes()
		fpHex := hex.EncodeToString(fp[:])
		fields = append(fields,
			zap.String("evidence_event_kind", string(ev.EventKind())),
			zap.String("evidence_channel", ev.Channel()),
			zap.String("evidence_filter_rule", ev.FilterRule()),
			zap.String("evidence_outcome", string(ev.Outcome())),
			zap.String("evidence_descriptor_code", ev.DescriptorCode()),
			zap.String("evidence_fingerprint", fpHex),
			zap.Int("evidence_count", ev.Count()),
			zap.Int("evidence_offset", ev.Offset()),
			zap.Time("evidence_timestamp", ev.Timestamp()),
		)
	}

	return s.core.Write(entry, fields)
}
