package oauth2_test

import (
	"bytes"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"

	"github.com/tidbcloud/auth-server/api"
	"github.com/tidbcloud/auth-server/commons/config"
	"github.com/tidbcloud/auth-server/commons/metrics"
	oauth2_ctl "github.com/tidbcloud/auth-server/internal/controller/oauth2"
	oauth2_dto "github.com/tidbcloud/auth-server/internal/dto/oauth2"
	oauth2_svc "github.com/tidbcloud/auth-server/internal/service/oauth2"
)

func SetupTestRouter() (*gin.Engine, *oauth2_svc.MockOauth2Service, *gomock.Controller) {
	gin.SetMode(gin.TestMode)
	// Set local mode to skip grpc auth middleware
	config.Global().ServerConfig.Env = "local"

	ctrl := gomock.NewController(nil)

	mockOauth2Svc := oauth2_svc.NewMockOauth2Service(ctrl)

	metrics.RegisterCentralMetrics()

	ginEng := gin.Default()
	api.Routers(&api.RouterManager{
		GinEng: ginEng,
		Oauth2Controller: oauth2_ctl.NewOauth2Controller(
			mockOauth2Svc,
		),
	})

	return ginEng, mockOauth2Svc, ctrl
}

const DeviceAuthorizationEndpoint = "/v1/device_authorization"

func TestDeviceAuthorizationEndpointMethodNotAllowed(t *testing.T) {
	ginEng, _, ctrl := SetupTestRouter()
	defer ctrl.Finish()

	notAllowedMethods := []string{"GET", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"}

	for _, method := range notAllowedMethods {
		t.Run(method, func(t *testing.T) {
			w := httptest.NewRecorder()
			req, _ := http.NewRequest(method, DeviceAuthorizationEndpoint, nil)
			ginEng.ServeHTTP(w, req)

			assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
		})
	}
}

func TestDeviceAuthorizationEndpointBadRequest(t *testing.T) {
	ginEng, _, ctrl := SetupTestRouter()
	defer ctrl.Finish()

	t.Run("EmptyBody", func(t *testing.T) {
		w := httptest.NewRecorder()
		req, _ := http.NewRequest("POST", DeviceAuthorizationEndpoint, nil)
		ginEng.ServeHTTP(w, req)

		assert.Equal(t, http.StatusBadRequest, w.Code)
		assert.Equal(t, "{\"error\":\"invalid_request\",\"error_description\":\"bad request body\"}", w.Body.String())
	})

	t.Run("InvalidBody", func(t *testing.T) {
		w := httptest.NewRecorder()
		req, _ := http.NewRequest("POST", DeviceAuthorizationEndpoint, nil)
		req.Header.Set("Content-Type", "application/json")
		ginEng.ServeHTTP(w, req)

		assert.Equal(t, http.StatusBadRequest, w.Code)
		assert.Equal(t, "{\"error\":\"invalid_request\",\"error_description\":\"bad request body\"}", w.Body.String())
	})

	t.Run("MissingClientID", func(t *testing.T) {
		w := httptest.NewRecorder()
		var jsonStr = []byte(`{}`)
		req, _ := http.NewRequest("POST", DeviceAuthorizationEndpoint, bytes.NewBuffer(jsonStr))
		req.Header.Set("Content-Type", "application/json")
		ginEng.ServeHTTP(w, req)

		assert.Equal(t, http.StatusBadRequest, w.Code)
		assert.Equal(t, "{\"error\":\"invalid_request\",\"error_description\":\"client_id is required\"}", w.Body.String())
	})

	t.Run("InvalidClientID", func(t *testing.T) {
		w := httptest.NewRecorder()
		var jsonStr = []byte(`{"client_id":1}`)
		req, _ := http.NewRequest("POST", DeviceAuthorizationEndpoint, bytes.NewBuffer(jsonStr))
		req.Header.Set("Content-Type", "application/json")
		ginEng.ServeHTTP(w, req)

		assert.Equal(t, http.StatusBadRequest, w.Code)
		assert.Equal(t, "{\"error\":\"invalid_request\",\"error_description\":\"bad request body\"}", w.Body.String())
	})
}

func TestDeviceAuthorizationEndpointSuccess(t *testing.T) {
	ginEng, mockOauth2Svc, ctrl := SetupTestRouter()
	defer ctrl.Finish()

	mockOauth2Svc.EXPECT().HandleDeviceAuthorizationRequest(gomock.Any(), "test", gomock.Any(), gomock.Any()).Return(
		&oauth2_dto.DeviceAuthorizationResponse{
			DeviceCode:              "test_device_code",
			UserCode:                "test_user_code",
			VerificationURI:         "http://localhost:8080/device",
			VerificationURIComplete: "http://localhost:8080/device?user_code=test_user_code",
			ExpiresIn:               1800,
			Interval:                5,
		}, nil,
	).AnyTimes()

	t.Run("JSONRequestSuccess", func(t *testing.T) {
		w := httptest.NewRecorder()
		var jsonStr = []byte(`{"client_id":"test"}`)
		req, _ := http.NewRequest("POST", DeviceAuthorizationEndpoint, bytes.NewBuffer(jsonStr))
		req.Header.Set("Content-Type", "application/json")
		ginEng.ServeHTTP(w, req)

		assert.Equal(t, http.StatusOK, w.Code)
		assert.Equal(t, "{\"device_code\":\"test_device_code\",\"user_code\":\"test_user_code\",\"verification_uri\":\"http://localhost:8080/device\",\"verification_uri_complete\":\"http://localhost:8080/device?user_code=test_user_code\",\"expires_in\":1800,\"interval\":5}", w.Body.String())
	})

	t.Run("FormRequestSuccess", func(t *testing.T) {
		w := httptest.NewRecorder()
		formData := url.Values{}
		formData.Set("client_id", "test")
		req, _ := http.NewRequest("POST", DeviceAuthorizationEndpoint, bytes.NewBufferString(formData.Encode()))
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
		ginEng.ServeHTTP(w, req)

		assert.Equal(t, http.StatusOK, w.Code)
		assert.Equal(t, "{\"device_code\":\"test_device_code\",\"user_code\":\"test_user_code\",\"verification_uri\":\"http://localhost:8080/device\",\"verification_uri_complete\":\"http://localhost:8080/device?user_code=test_user_code\",\"expires_in\":1800,\"interval\":5}", w.Body.String())
	})

}
