package cql

import (
	"testing"
	"time"
)

func TestComputeProcessingTime(t *testing.T) {
	tests := []struct {
		name      string
		total     time.Duration
		frameRead time.Duration
		want      time.Duration
	}{
		{
			name:      "subtract frame read from total",
			total:     120 * time.Millisecond,
			frameRead: 80 * time.Millisecond,
			want:      40 * time.Millisecond,
		},
		{
			name:      "equal durations clamp to zero",
			total:     50 * time.Millisecond,
			frameRead: 50 * time.Millisecond,
			want:      0,
		},
		{
			name:      "frame read greater than total clamps to zero",
			total:     30 * time.Millisecond,
			frameRead: 60 * time.Millisecond,
			want:      0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := computeProcessingTime(tt.total, tt.frameRead)
			if got != tt.want {
				t.Fatalf("computeProcessingTime(%s, %s) = %s, want %s", tt.total, tt.frameRead, got, tt.want)
			}
		})
	}
}

func TestComputeFrameReadDuration(t *testing.T) {
	tests := []struct {
		name          string
		firstByteWait time.Duration
		bodyRead      time.Duration
		want          time.Duration
	}{
		{
			name:          "adds first byte wait and body read",
			firstByteWait: 20 * time.Millisecond,
			bodyRead:      30 * time.Millisecond,
			want:          50 * time.Millisecond,
		},
		{
			name:          "negative values clamp to zero",
			firstByteWait: -10 * time.Millisecond,
			bodyRead:      5 * time.Millisecond,
			want:          5 * time.Millisecond,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := computeFrameReadDuration(tt.firstByteWait, tt.bodyRead)
			if got != tt.want {
				t.Fatalf("computeFrameReadDuration(%s, %s) = %s, want %s", tt.firstByteWait, tt.bodyRead, got, tt.want)
			}
		})
	}
}

func TestRequestMetricsExcludeFirstByteWaitFromExportedDurations(t *testing.T) {
	total := 20 * time.Second
	firstByteWait := 19900 * time.Millisecond
	bodyRead := 25 * time.Millisecond

	slowLogFrameRead := computeFrameReadDuration(firstByteWait, bodyRead)
	exportedFrameRead := bodyRead
	processingTime := computeProcessingTime(total, slowLogFrameRead)

	if slowLogFrameRead != 19925*time.Millisecond {
		t.Fatalf("slow-log frame read = %s, want 19.925s", slowLogFrameRead)
	}
	if exportedFrameRead != 25*time.Millisecond {
		t.Fatalf("exported frame read metric = %s, want 25ms", exportedFrameRead)
	}
	if processingTime != 75*time.Millisecond {
		t.Fatalf("processing time = %s, want 75ms", processingTime)
	}
}

func TestComputeHandlerOtherDuration(t *testing.T) {
	tests := []struct {
		name       string
		handler    time.Duration
		schemaSync time.Duration
		schema     time.Duration
		meta       time.Duration
		mysqlWait  time.Duration
		mysqlQuery time.Duration
		encode     time.Duration
		want       time.Duration
	}{
		{
			name:       "returns uncovered handler time",
			handler:    300 * time.Millisecond,
			schemaSync: 0,
			schema:     20 * time.Millisecond,
			meta:       0,
			mysqlWait:  5 * time.Millisecond,
			mysqlQuery: 150 * time.Millisecond,
			encode:     1 * time.Millisecond,
			want:       124 * time.Millisecond,
		},
		{
			name:       "clamps negative to zero",
			handler:    100 * time.Millisecond,
			schemaSync: 0,
			schema:     40 * time.Millisecond,
			meta:       0,
			mysqlWait:  30 * time.Millisecond,
			mysqlQuery: 40 * time.Millisecond,
			encode:     2 * time.Millisecond,
			want:       0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := computeHandlerOtherDuration(
				tt.handler,
				tt.schemaSync,
				tt.schema,
				tt.meta,
				tt.mysqlWait,
				tt.mysqlQuery,
				tt.encode,
			)
			if got != tt.want {
				t.Fatalf("computeHandlerOtherDuration(...) = %s, want %s", got, tt.want)
			}
		})
	}
}

func TestFrameReadObserverBreakdown(t *testing.T) {
	obs := &frameReadObserver{
		bytesRead:     128,
		totalRead:     50 * time.Millisecond,
		firstByteWait: 20 * time.Millisecond,
		sawFirstByte:  true,
	}
	requestBytes, firstByteWait, bodyRead := obs.Breakdown(5 * time.Millisecond)
	if requestBytes != 128 {
		t.Fatalf("expected request bytes 128, got %d", requestBytes)
	}
	if firstByteWait != 25*time.Millisecond {
		t.Fatalf("expected first byte wait 25ms, got %s", firstByteWait)
	}
	if bodyRead != 30*time.Millisecond {
		t.Fatalf("expected body read 30ms, got %s", bodyRead)
	}
}
