package metrics

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

const (
	// tenantSeriesTTL is intentionally independent of tenant pool idle/LRU
	// eviction. Resource-cache eviction does not prove that the tenant is
	// inactive, especially when each pod has its own pool.
	tenantSeriesTTL             = 15 * time.Minute
	tenantSeriesSweepInterval   = time.Minute
	tenantEvictionReasonTTL     = "ttl"
	tenantEvictionReasonDeleted = "deleted"
)

type tenantSeriesRef struct {
	kind   instrumentKind
	name   string
	labels string
}

type tenantSeriesState struct {
	lastSeen time.Time
	refs     map[tenantSeriesRef]struct{}
}

type tenantSeriesRetention struct {
	now         func() time.Time
	nextSweepAt time.Time
	tenants     map[string]*tenantSeriesState
	evictions   map[string]int64
}

func (r *Registry) configureTenantSeriesRetention(now func() time.Time) {
	current := now()
	r.mu.Lock()
	defer r.mu.Unlock()
	r.tenantRetention.now = now
	r.tenantRetention.nextSweepAt = current.Add(tenantSeriesSweepInterval)
}

func tenantIDFromAttributes(attrs []Attribute) string {
	for _, attr := range attrs {
		if normalizeLabelKey(attr.Key) == "tenant_id" {
			tenantID := strings.TrimSpace(attr.Value)
			if tenantID == "" || tenantID == "unknown" {
				return ""
			}
			return tenantID
		}
	}
	return ""
}

func (r *Registry) admitTenantSeriesLocked(tenantID string, now time.Time) *tenantSeriesState {
	if tenantID == "" {
		return nil
	}
	r.sweepExpiredTenantSeriesLocked(now, false)
	if state := r.tenantRetention.tenants[tenantID]; state != nil {
		state.lastSeen = now
		return state
	}
	state := &tenantSeriesState{lastSeen: now, refs: make(map[tenantSeriesRef]struct{})}
	r.tenantRetention.tenants[tenantID] = state
	return state
}

func indexTenantSeriesLocked(state *tenantSeriesState, ref tenantSeriesRef) {
	if state != nil {
		state.refs[ref] = struct{}{}
	}
}

func removeTenantSeriesRefFromStateLocked(state *tenantSeriesState, ref tenantSeriesRef) {
	if state != nil {
		delete(state.refs, ref)
	}
}

func (r *Registry) removeTenantSeriesRefLocked(tenantID string, ref tenantSeriesRef) {
	removeTenantSeriesRefFromStateLocked(r.tenantRetention.tenants[tenantID], ref)
}

func (r *Registry) recordTenantActivity(tenantID string) {
	tenantID = strings.TrimSpace(tenantID)
	if tenantID == "" || tenantID == "unknown" {
		return
	}
	r.mu.Lock()
	defer r.mu.Unlock()
	r.admitTenantSeriesLocked(tenantID, r.tenantRetention.now())
}

func (r *Registry) activeTenantIDs() map[string]struct{} {
	r.mu.RLock()
	defer r.mu.RUnlock()
	active := make(map[string]struct{}, len(r.tenantRetention.tenants))
	for tenantID := range r.tenantRetention.tenants {
		active[tenantID] = struct{}{}
	}
	return active
}

func (r *Registry) sweepTenantSeriesIfDue() {
	r.mu.Lock()
	defer r.mu.Unlock()
	now := r.tenantRetention.now()
	r.sweepExpiredTenantSeriesLocked(now, false)
}

func (r *Registry) sweepExpiredTenantSeriesLocked(now time.Time, force bool) {
	if !force && now.Before(r.tenantRetention.nextSweepAt) {
		return
	}
	for tenantID, state := range r.tenantRetention.tenants {
		if now.Sub(state.lastSeen) >= tenantSeriesTTL {
			r.removeTenantSeriesLocked(tenantID, tenantEvictionReasonTTL)
		}
	}
	r.tenantRetention.nextSweepAt = now.Add(tenantSeriesSweepInterval)
}

func (r *Registry) removeTenantSeriesLocked(tenantID, reason string) {
	state := r.tenantRetention.tenants[tenantID]
	if state == nil {
		return
	}
	for ref := range state.refs {
		switch ref.kind {
		case instrumentCounter:
			if inst := r.counters[ref.name]; inst != nil {
				delete(inst.values, ref.labels)
			}
		case instrumentGauge:
			if inst := r.gauges[ref.name]; inst != nil {
				delete(inst.values, ref.labels)
			}
		case instrumentHistogram:
			if inst := r.histograms[ref.name]; inst != nil {
				delete(inst.values, ref.labels)
			}
		}
	}
	delete(r.tenantQuotaSnapshots, tenantID)
	delete(r.tenantRetention.tenants, tenantID)
	// Keep the billing ops flush tracker in sync with series eviction so a
	// reactivated tenant starts from its fresh counter values.
	fileOpsFlush.dropTenant(tenantID)
	r.tenantRetention.evictions[reason]++
}

// DeleteTenantSeries immediately removes every metric series indexed for a tenant.
func (r *Registry) DeleteTenantSeries(tenantID string) {
	if r == nil {
		return
	}
	tenantID = strings.TrimSpace(tenantID)
	if tenantID == "" || tenantID == "unknown" {
		return
	}
	r.mu.Lock()
	defer r.mu.Unlock()
	r.removeTenantSeriesLocked(tenantID, tenantEvictionReasonDeleted)
}

func (r *Registry) writeTenantRetentionMetrics(w io.Writer) {
	r.mu.RLock()
	ttlEvictions := r.tenantRetention.evictions[tenantEvictionReasonTTL]
	deletedEvictions := r.tenantRetention.evictions[tenantEvictionReasonDeleted]
	r.mu.RUnlock()

	_, _ = fmt.Fprintln(w, "# HELP drive9_metrics_tenant_evictions_total Tenant metric registry evictions by reason")
	_, _ = fmt.Fprintln(w, "# TYPE drive9_metrics_tenant_evictions_total counter")
	writeMetricLine(w, "drive9_metrics_tenant_evictions_total", `reason="deleted"`, fmt.Sprintf("%d", deletedEvictions))
	writeMetricLine(w, "drive9_metrics_tenant_evictions_total", `reason="ttl"`, fmt.Sprintf("%d", ttlEvictions))
}
