package frontend

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

	"github.com/prometheus/client_golang/prometheus"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/grafana/grafana/pkg/api/dtos"
	"github.com/grafana/grafana/pkg/services/contexthandler"
	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
	"github.com/grafana/grafana/pkg/services/featuremgmt"
	"github.com/grafana/grafana/pkg/services/hooks"
	"github.com/grafana/grafana/pkg/services/licensing"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/grafana/grafana/pkg/web"
)

// Helper function to create a test service with minimal configuration
func createTestService(t *testing.T, cfg *setting.Cfg) *frontendService {
	t.Helper()

	features := featuremgmt.WithFeatures()
	license := &licensing.OSSLicensingService{}
	hooksService := hooks.ProvideService()

	var promRegister prometheus.Registerer = prometheus.NewRegistry()
	promGatherer := promRegister.(*prometheus.Registry)

	if cfg.BuildVersion == "" {
		cfg.BuildVersion = "10.3.0"
	}

	service, err := ProvideFrontendService(cfg, features, promGatherer, promRegister, license, hooksService)
	require.NoError(t, err)

	return service
}

func TestFrontendService_ServerCreation(t *testing.T) {
	t.Run("should create HTTP server with correct configuration", func(t *testing.T) {
		publicDir := setupTestWebAssets(t)
		cfg := &setting.Cfg{
			HTTPPort:       "1234",
			StaticRootPath: publicDir,
		}
		service := createTestService(t, cfg)

		ctx := context.Background()
		server := service.newFrontendServer(ctx)

		assert.NotNil(t, server)
		assert.Equal(t, ":1234", server.Addr)
		assert.NotNil(t, server.Handler)
		assert.NotNil(t, server.BaseContext)
	})
}

func TestFrontendService_Routes(t *testing.T) {
	publicDir := setupTestWebAssets(t)
	cfg := &setting.Cfg{
		HTTPPort:       "3000",
		StaticRootPath: publicDir,
	}
	service := createTestService(t, cfg)

	// Create a test mux to verify route registration
	mux := web.New()
	service.addMiddlewares(mux)
	service.registerRoutes(mux)

	t.Run("should handle frontend wildcard routes", func(t *testing.T) {
		// Test that routes are registered by making requests
		testCases := []struct {
			path        string
			description string
		}{
			// Metrics isn't registered in the test service?
			{"/", "index route should return HTML"},
			{"/dashboards", "browse dashboards route should return HTML"},
			{"/d/de773f33s8qgwf/fep-homepage", "dashboard route should return HTML"},
		}

		for _, tc := range testCases {
			t.Run(tc.description, func(t *testing.T) {
				req := httptest.NewRequest("GET", tc.path, nil)
				recorder := httptest.NewRecorder()

				mux.ServeHTTP(recorder, req)

				assert.Equal(t, 200, recorder.Code)
				assert.Contains(t, recorder.Body.String(), "<div id=\"reactRoot\"></div>")
			})
		}
	})

	t.Run("should handle assets 404 correctly", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/public/build/app.js", nil)
		recorder := httptest.NewRecorder()

		mux.ServeHTTP(recorder, req)

		assert.Equal(t, 404, recorder.Code)
		assert.Equal(t, "404 page not found", strings.TrimSpace(recorder.Body.String()))
	})

	t.Run("should return prometheus metrics", func(t *testing.T) {
		testCounter := prometheus.NewCounter(prometheus.CounterOpts{
			Name: "shrimp_count",
		})
		err := service.promRegister.Register(testCounter)
		require.NoError(t, err)
		testCounter.Inc()

		req := httptest.NewRequest("GET", "/metrics", nil)
		recorder := httptest.NewRecorder()

		mux.ServeHTTP(recorder, req)

		assert.Contains(t, recorder.Body.String(), "\nshrimp_count 1\n")
	})

	t.Run("should return health status correctly", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/-/health", nil)
		recorder := httptest.NewRecorder()

		mux.ServeHTTP(recorder, req)

		assert.Equal(t, 200, recorder.Code)
		assert.Equal(t, "OK", strings.TrimSpace(recorder.Body.String()))
	})
}

func TestFrontendService_Middleware(t *testing.T) {
	publicDir := setupTestWebAssets(t)
	cfg := &setting.Cfg{
		HTTPPort:       "3000",
		StaticRootPath: publicDir,
	}

	t.Run("should register route prom metrics", func(t *testing.T) {
		service := createTestService(t, cfg)
		mux := web.New()
		service.addMiddlewares(mux)
		service.registerRoutes(mux)

		req := httptest.NewRequest("GET", "/dashboards", nil)
		recorder := httptest.NewRecorder()
		mux.ServeHTTP(recorder, req)

		req = httptest.NewRequest("GET", "/public/build/app.js", nil)
		mux.ServeHTTP(recorder, req)

		req = httptest.NewRequest("GET", "/metrics", nil)
		mux.ServeHTTP(recorder, req)

		metricsBody := recorder.Body.String()
		assert.Contains(t, metricsBody, "# TYPE grafana_http_request_duration_seconds histogram")
		assert.Contains(t, metricsBody, "grafana_http_request_duration_seconds_bucket{handler=\"public-build-assets\"") // assets 404
		assert.Contains(t, metricsBody, "grafana_http_request_duration_seconds_bucket{handler=\"/*\"")                  // index route
	})

	t.Run("should add context middleware", func(t *testing.T) {
		service := createTestService(t, cfg)
		mux := web.New()
		service.addMiddlewares(mux)

		mux.Get("/test-route", func(w http.ResponseWriter, r *http.Request) {
			ctx := contexthandler.FromContext(r.Context())
			assert.NotNil(t, ctx)
			assert.NotNil(t, ctx.Context)
			assert.NotNil(t, ctx.Logger)

			w.WriteHeader(200)
			_, err := w.Write([]byte("ok"))
			require.NoError(t, err)
		})

		req := httptest.NewRequest("GET", "/test-route", nil)
		recorder := httptest.NewRecorder()
		mux.ServeHTTP(recorder, req)
	})
}

func TestFrontendService_IndexHooks(t *testing.T) {
	publicDir := setupTestWebAssets(t)
	cfg := &setting.Cfg{
		HTTPPort:       "3000",
		StaticRootPath: publicDir,
		BuildVersion:   "10.3.0",
	}

	t.Run("should handle hooks that modify buildInfo fields", func(t *testing.T) {
		service := createTestService(t, cfg)

		// Add a hook that modifies various buildInfo fields
		service.index.hooksService.AddIndexDataHook(func(indexData *dtos.IndexViewData, req *contextmodel.ReqContext) {
			indexData.Settings.BuildInfo.Version = "99.99.99"
			indexData.Settings.BuildInfo.VersionString = "Custom Edition v99.99.99 (custom)"
			indexData.Settings.BuildInfo.Edition = "custom-edition"
		})

		mux := web.New()
		service.addMiddlewares(mux)
		service.registerRoutes(mux)

		req := httptest.NewRequest("GET", "/", nil)
		recorder := httptest.NewRecorder()
		mux.ServeHTTP(recorder, req)

		assert.Equal(t, 200, recorder.Code)
		body := recorder.Body.String()
		assert.Contains(t, body, "99.99.99", "Hook should have modified the version")
		assert.Contains(t, body, "Custom Edition v99.99.99 (custom)", "Hook should have modified the version string")
		assert.Contains(t, body, "custom-edition", "Hook should have modified the edition")
	})

	t.Run("should work without any hooks registered", func(t *testing.T) {
		service := createTestService(t, cfg)

		mux := web.New()
		service.addMiddlewares(mux)
		service.registerRoutes(mux)

		req := httptest.NewRequest("GET", "/", nil)
		recorder := httptest.NewRecorder()
		mux.ServeHTTP(recorder, req)

		assert.Equal(t, 200, recorder.Code)
		body := recorder.Body.String()
		assert.Contains(t, body, "<div id=\"reactRoot\"></div>")
		// The build version comes from setting.BuildVersion (global), not cfg.BuildVersion
		// So we just check that the page renders successfully
		assert.Contains(t, body, "window.grafanaBootData")
	})
}
