package permissions

import (
	"context"
	"sync"
	"time"
)

type diagnosticsContextKey struct{}

// Diagnostics aggregates permission-path timings for one request.
type Diagnostics struct {
	mu sync.Mutex

	systemCalls     int
	systemDuration  time.Duration
	customCalls     int
	customDuration  time.Duration
	recordTypeCalls int
	recordTypeRows  int
	recordTypeDur   time.Duration

	objectDBFallbacks int
	objectDBDuration  time.Duration
	objectCacheHits   int
	fieldDBFallbacks  int
	fieldDBDuration   time.Duration
	fieldCacheHits    int

	objectBulkCalls    int
	objectBulkRows     int
	objectBulkDuration time.Duration
	fieldBulkCalls     int
	fieldBulkRows      int
	fieldBulkDuration  time.Duration
	fieldBulkRejected  int

	effectiveAccessCalls    int
	effectiveAccessFields   int
	effectiveAccessDuration time.Duration
}

// DiagnosticsSnapshot is a stable copy for request logging.
type DiagnosticsSnapshot struct {
	SystemCalls     int
	SystemDuration  time.Duration
	CustomCalls     int
	CustomDuration  time.Duration
	RecordTypeCalls int
	RecordTypeRows  int
	RecordTypeDur   time.Duration

	ObjectDBFallbacks int
	ObjectDBDuration  time.Duration
	ObjectCacheHits   int
	FieldDBFallbacks  int
	FieldDBDuration   time.Duration
	FieldCacheHits    int

	ObjectBulkCalls    int
	ObjectBulkRows     int
	ObjectBulkDuration time.Duration
	FieldBulkCalls     int
	FieldBulkRows      int
	FieldBulkDuration  time.Duration
	FieldBulkRejected  int

	EffectiveAccessCalls    int
	EffectiveAccessFields   int
	EffectiveAccessDuration time.Duration
}

func NewDiagnostics() *Diagnostics {
	return &Diagnostics{}
}

func WithDiagnostics(ctx context.Context, diag *Diagnostics) context.Context {
	if ctx == nil || diag == nil {
		return ctx
	}
	return context.WithValue(ctx, diagnosticsContextKey{}, diag)
}

func diagnosticsFromContext(ctx context.Context) *Diagnostics {
	if ctx == nil {
		return nil
	}
	diag, _ := ctx.Value(diagnosticsContextKey{}).(*Diagnostics)
	return diag
}

func SnapshotDiagnostics(ctx context.Context) DiagnosticsSnapshot {
	if diag := diagnosticsFromContext(ctx); diag != nil {
		return diag.Snapshot()
	}
	return DiagnosticsSnapshot{}
}

func (d *Diagnostics) Snapshot() DiagnosticsSnapshot {
	if d == nil {
		return DiagnosticsSnapshot{}
	}
	d.mu.Lock()
	defer d.mu.Unlock()
	return DiagnosticsSnapshot{
		SystemCalls:             d.systemCalls,
		SystemDuration:          d.systemDuration,
		CustomCalls:             d.customCalls,
		CustomDuration:          d.customDuration,
		RecordTypeCalls:         d.recordTypeCalls,
		RecordTypeRows:          d.recordTypeRows,
		RecordTypeDur:           d.recordTypeDur,
		ObjectDBFallbacks:       d.objectDBFallbacks,
		ObjectDBDuration:        d.objectDBDuration,
		ObjectCacheHits:         d.objectCacheHits,
		FieldDBFallbacks:        d.fieldDBFallbacks,
		FieldDBDuration:         d.fieldDBDuration,
		FieldCacheHits:          d.fieldCacheHits,
		ObjectBulkCalls:         d.objectBulkCalls,
		ObjectBulkRows:          d.objectBulkRows,
		ObjectBulkDuration:      d.objectBulkDuration,
		FieldBulkCalls:          d.fieldBulkCalls,
		FieldBulkRows:           d.fieldBulkRows,
		FieldBulkDuration:       d.fieldBulkDuration,
		FieldBulkRejected:       d.fieldBulkRejected,
		EffectiveAccessCalls:    d.effectiveAccessCalls,
		EffectiveAccessFields:   d.effectiveAccessFields,
		EffectiveAccessDuration: d.effectiveAccessDuration,
	}
}

func (s DiagnosticsSnapshot) HasWork() bool {
	return s.SystemCalls+s.CustomCalls+s.RecordTypeCalls+
		s.ObjectDBFallbacks+s.FieldDBFallbacks+
		s.ObjectBulkCalls+s.FieldBulkCalls+s.FieldBulkRejected+
		s.EffectiveAccessCalls > 0
}

func recordSystemPermissionLoad(ctx context.Context, duration time.Duration) {
	if diag := diagnosticsFromContext(ctx); diag != nil {
		diag.mu.Lock()
		diag.systemCalls++
		diag.systemDuration += duration
		diag.mu.Unlock()
	}
}

func recordCustomPermissionLoad(ctx context.Context, duration time.Duration) {
	if diag := diagnosticsFromContext(ctx); diag != nil {
		diag.mu.Lock()
		diag.customCalls++
		diag.customDuration += duration
		diag.mu.Unlock()
	}
}

func recordRecordTypePermissionLoad(ctx context.Context, rows int, duration time.Duration) {
	if diag := diagnosticsFromContext(ctx); diag != nil {
		diag.mu.Lock()
		diag.recordTypeCalls++
		diag.recordTypeRows += rows
		diag.recordTypeDur += duration
		diag.mu.Unlock()
	}
}

func recordObjectPermissionCacheHit(ctx context.Context) {
	if diag := diagnosticsFromContext(ctx); diag != nil {
		diag.mu.Lock()
		diag.objectCacheHits++
		diag.mu.Unlock()
	}
}

func recordFieldPermissionCacheHit(ctx context.Context) {
	if diag := diagnosticsFromContext(ctx); diag != nil {
		diag.mu.Lock()
		diag.fieldCacheHits++
		diag.mu.Unlock()
	}
}

func recordObjectPermissionDBFallback(ctx context.Context, duration time.Duration) {
	if diag := diagnosticsFromContext(ctx); diag != nil {
		diag.mu.Lock()
		diag.objectDBFallbacks++
		diag.objectDBDuration += duration
		diag.mu.Unlock()
	}
}

func recordFieldPermissionDBFallback(ctx context.Context, duration time.Duration) {
	if diag := diagnosticsFromContext(ctx); diag != nil {
		diag.mu.Lock()
		diag.fieldDBFallbacks++
		diag.fieldDBDuration += duration
		diag.mu.Unlock()
	}
}

func recordObjectPermissionBulkLoad(ctx context.Context, rows int, duration time.Duration) {
	if diag := diagnosticsFromContext(ctx); diag != nil {
		diag.mu.Lock()
		diag.objectBulkCalls++
		diag.objectBulkRows += rows
		diag.objectBulkDuration += duration
		diag.mu.Unlock()
	}
}

func recordFieldPermissionBulkLoad(ctx context.Context, rows int, duration time.Duration) {
	if diag := diagnosticsFromContext(ctx); diag != nil {
		diag.mu.Lock()
		diag.fieldBulkCalls++
		diag.fieldBulkRows += rows
		diag.fieldBulkDuration += duration
		diag.mu.Unlock()
	}
}

func recordFieldPermissionBulkRejection(ctx context.Context) {
	if diag := diagnosticsFromContext(ctx); diag != nil {
		diag.mu.Lock()
		diag.fieldBulkRejected++
		diag.mu.Unlock()
	}
}

func recordEffectiveAccessLoad(ctx context.Context, fields int, duration time.Duration) {
	if diag := diagnosticsFromContext(ctx); diag != nil {
		diag.mu.Lock()
		diag.effectiveAccessCalls++
		diag.effectiveAccessFields += fields
		diag.effectiveAccessDuration += duration
		diag.mu.Unlock()
	}
}
