package m2mcore

import (
	"fmt"
	"io"
	"sort"
	"strings"
	"sync"
	"time"
)

// Recorder — интерфейс записи метрик. NoopRecorder используется по
// умолчанию; реальная реализация (Prometheus) подключается в M2, когда
// внешние Go-зависимости становятся доступны.
type Recorder interface {
	StageDuration(stage State, d time.Duration)
	IncDeal(state State)
	IncSLABreach(stage State, budget string)
}

// NoopRecorder ничего не пишет.
type NoopRecorder struct{}

// StageDuration — no-op.
func (NoopRecorder) StageDuration(State, time.Duration) {}

// IncDeal — no-op.
func (NoopRecorder) IncDeal(State) {}

// IncSLABreach — no-op.
func (NoopRecorder) IncSLABreach(State, string) {}

// MemoryRecorder — простая in-memory реализация Recorder, удобная для
// тестов и для /metrics-endpoint в M1 (Prometheus text format).
type MemoryRecorder struct {
	mu sync.Mutex
	// stageDurations: stage -> сумма длительностей и счётчик
	stageDurations map[State]struct {
		Sum   time.Duration
		Count uint64
	}
	dealsByState map[State]uint64
	slaBreaches  map[string]uint64 // ключ: "stage|budget"
}

// NewMemoryRecorder создаёт MemoryRecorder с пустыми счётчиками.
func NewMemoryRecorder() *MemoryRecorder {
	return &MemoryRecorder{
		stageDurations: make(map[State]struct {
			Sum   time.Duration
			Count uint64
		}),
		dealsByState: make(map[State]uint64),
		slaBreaches:  make(map[string]uint64),
	}
}

// StageDuration фиксирует длительность этапа FSM.
func (m *MemoryRecorder) StageDuration(stage State, d time.Duration) {
	m.mu.Lock()
	defer m.mu.Unlock()
	v := m.stageDurations[stage]
	v.Sum += d
	v.Count++
	m.stageDurations[stage] = v
}

// IncDeal увеличивает счётчик сделок в заданном итоговом состоянии.
func (m *MemoryRecorder) IncDeal(state State) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.dealsByState[state]++
}

// IncSLABreach фиксирует превышение SLA-бюджета на этапе.
func (m *MemoryRecorder) IncSLABreach(stage State, budget string) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.slaBreaches[fmt.Sprintf("%s|%s", stage, budget)]++
}

// WritePrometheus сериализует накопленные метрики в формате Prometheus
// text exposition (HELP/TYPE + значения). Подходит для /metrics.
func (m *MemoryRecorder) WritePrometheus(w io.Writer) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	fmt.Fprintf(w, "# HELP m2m_stage_duration_seconds_sum Сумма длительностей этапа в секундах\n")
	fmt.Fprintf(w, "# TYPE m2m_stage_duration_seconds_sum counter\n")
	stages := sortedStates(m.stageDurations)
	for _, s := range stages {
		v := m.stageDurations[s]
		fmt.Fprintf(w, "m2m_stage_duration_seconds_sum{stage=%q} %f\n", s, v.Sum.Seconds())
		fmt.Fprintf(w, "m2m_stage_duration_seconds_count{stage=%q} %d\n", s, v.Count)
	}

	fmt.Fprintf(w, "# HELP m2m_deals_total Сделки по итоговому состоянию\n")
	fmt.Fprintf(w, "# TYPE m2m_deals_total counter\n")
	dstates := sortedStatesCount(m.dealsByState)
	for _, s := range dstates {
		fmt.Fprintf(w, "m2m_deals_total{state=%q} %d\n", s, m.dealsByState[s])
	}

	fmt.Fprintf(w, "# HELP m2m_sla_breaches_total Превышения SLA-бюджета\n")
	fmt.Fprintf(w, "# TYPE m2m_sla_breaches_total counter\n")
	keys := make([]string, 0, len(m.slaBreaches))
	for k := range m.slaBreaches {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		stage, budget, ok := strings.Cut(k, "|")
		if !ok {
			continue
		}
		fmt.Fprintf(w, "m2m_sla_breaches_total{stage=%q,budget=%q} %d\n", stage, budget, m.slaBreaches[k])
	}
	return nil
}

// sortedStates возвращает состояния как отсортированный слайс для
// детерминированного вывода.
func sortedStates(m map[State]struct {
	Sum   time.Duration
	Count uint64
}) []State {
	out := make([]State, 0, len(m))
	for k := range m {
		out = append(out, k)
	}
	sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
	return out
}

func sortedStatesCount(m map[State]uint64) []State {
	out := make([]State, 0, len(m))
	for k := range m {
		out = append(out, k)
	}
	sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
	return out
}
