package query

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/prometheus/client_golang/prometheus"
	dto "github.com/prometheus/client_model/go"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestGitHubUsernameFromContext(t *testing.T) {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	assert.Equal(t, "", githubUsernameFromContext(c))
	c.Set(contextKeyGitHubUsername, "alice")
	assert.Equal(t, "alice", githubUsernameFromContext(c))
}

func TestApplyExecutionStatusLogsOnce(t *testing.T) {
	InitMetrics()
	beforeQueries := counterValue(t, olapQueriesTotal, "bob", StatusSucceeded, DatasetLogStats, "us-west-2")
	beforeBytes := counterValue(t, olapScannedBytesTotal, "bob", StatusSucceeded, DatasetLogStats, "us-west-2")

	s := newTestService(&fakeEngine{}, "us-west-2")
	s.cfg.AthenaWorkGroup = "o11y-olap"
	job := s.jobs.create(&Job{
		GitHubUsername: "bob",
		EngineName:     "fake",
		ExecutionID:    "exec-x",
		Dataset:        DatasetLogStats,
		Region:         "us-west-2",
		Status:         StatusRunning,
		SubmittedAt:    time.Now().UTC(),
	})

	now := time.Now().UTC()
	st := &ExecutionStatus{
		Status:                    StatusSucceeded,
		ScannedBytes:              12345,
		EngineExecutionTimeMillis: 88,
		TotalExecutionTimeMillis:  120,
		QueryQueueTimeMillis:      10,
		QueryPlanningTimeMillis:   5,
		CompletedAt:               &now,
	}
	updated := s.applyExecutionStatus(job.ID, st)
	require.NotNil(t, updated)
	assert.True(t, updated.UsageLogged)
	assert.Equal(t, int64(12345), updated.ScannedBytes)
	assert.Equal(t, int64(88), updated.EngineExecutionTimeMillis)

	assert.Equal(t, beforeQueries+1, counterValue(t, olapQueriesTotal, "bob", StatusSucceeded, DatasetLogStats, "us-west-2"))
	assert.Equal(t, beforeBytes+12345, counterValue(t, olapScannedBytesTotal, "bob", StatusSucceeded, DatasetLogStats, "us-west-2"))

	// Second terminal apply must not clear UsageLogged or panic; still idempotent.
	st2 := *st
	st2.ScannedBytes = 99999
	updated2 := s.applyExecutionStatus(job.ID, &st2)
	require.NotNil(t, updated2)
	assert.True(t, updated2.UsageLogged)
	assert.Equal(t, int64(99999), updated2.ScannedBytes) // stats still refresh
	// Metrics stay one-shot (UsageLogged already set).
	assert.Equal(t, beforeQueries+1, counterValue(t, olapQueriesTotal, "bob", StatusSucceeded, DatasetLogStats, "us-west-2"))
}

func counterValue(t *testing.T, vec *prometheus.CounterVec, labels ...string) float64 {
	t.Helper()
	m := &dto.Metric{}
	require.NoError(t, vec.WithLabelValues(labels...).Write(m))
	return m.GetCounter().GetValue()
}

func TestHandleSubmitRecordsGitHubUsername(t *testing.T) {
	gin.SetMode(gin.TestMode)
	engine := &fakeEngine{statusSeq: []string{StatusSucceeded}}
	s := newTestService(engine, "us-east-1")
	r := gin.New()
	r.Use(func(c *gin.Context) {
		c.Set(contextKeyGitHubUsername, "carol")
		c.Next()
	})
	InitOlapApi(r.Group("/api/v1"), s)

	body := `{"sql":"SELECT 1","wait_seconds":0}`
	req := httptest.NewRequest(http.MethodPost, "/api/v1/olap", strings.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("x-provider", "aws")
	req.Header.Set("x-region", "us-east-1")
	req.Header.Set("x-cluster-id", "cluster-1")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	require.Equal(t, http.StatusAccepted, w.Code)

	require.Len(t, s.jobs.jobs, 1)
	for _, j := range s.jobs.jobs {
		assert.Equal(t, "carol", j.GitHubUsername)
		assert.Equal(t, DatasetRaw, j.Dataset)
	}
}
