package metrics

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

type Metrics struct {
	start time.Time

	requests   atomic.Int64
	ok         atomic.Int64
	failed     atomic.Int64
	rateLimited atomic.Int64
	dbUpdates  atomic.Int64

	mu      sync.Mutex
	perHost map[string]*hostCounters
}

type hostCounters struct {
	requests, ok, failed, rateLimited atomic.Int64
}

func New() *Metrics {
	return &Metrics{start: time.Now(), perHost: make(map[string]*hostCounters)}
}

func (m *Metrics) host(name string) *hostCounters {
	m.mu.Lock()
	defer m.mu.Unlock()
	h, ok := m.perHost[name]
	if !ok {
		h = &hostCounters{}
		m.perHost[name] = h
	}
	return h
}

func (m *Metrics) Request(host string) {
	m.requests.Add(1)
	m.host(host).requests.Add(1)
}

func (m *Metrics) OK(host string) {
	m.ok.Add(1)
	m.host(host).ok.Add(1)
}

func (m *Metrics) Fail(host string) {
	m.failed.Add(1)
	m.host(host).failed.Add(1)
}

func (m *Metrics) RateLimited(host string) {
	m.rateLimited.Add(1)
	m.host(host).rateLimited.Add(1)
}

func (m *Metrics) DBUpdate(n int64) {
	m.dbUpdates.Add(n)
}

func (m *Metrics) Elapsed() time.Duration {
	return time.Since(m.start)
}

func (m *Metrics) Snapshot() string {
	elapsed := m.Elapsed()
	secs := elapsed.Seconds()
	if secs < 1 {
		secs = 1
	}
	total := m.requests.Load()
	return fmt.Sprintf(
		"[metrics] elapsed=%s requests=%d ok=%d fail=%d 429=%d (%.1f req/s) db_updates=%d",
		elapsed.Truncate(time.Second), total, m.ok.Load(), m.failed.Load(),
		m.rateLimited.Load(), float64(total)/secs, m.dbUpdates.Load(),
	)
}

func (m *Metrics) HostSummary() string {
	m.mu.Lock()
	defer m.mu.Unlock()
	out := ""
	for name, h := range m.perHost {
		out += fmt.Sprintf("  %s: req=%d ok=%d fail=%d 429=%d\n",
			name, h.requests.Load(), h.ok.Load(), h.failed.Load(), h.rateLimited.Load())
	}
	return out
}
