package oauth2_test

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

	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"

	oauth2_dto "github.com/tidbcloud/auth-server/internal/dto/oauth2"
	tierrs "github.com/tidbcloud/tiinf/errs"
)

const (
	InnerDeviceCodeCheckEndpoint        = "/api/v1/oauth/device/check"
	InnerDeviceCodeConfirmationEndpoint = "/api/v1/oauth/device/confirmation"
)

func setTestAuthHeader(req *http.Request) {
	req.Header.Set("X-Test-User-Email", "test@mail.com")
	req.Header.Set("X-Test-User-ID", "1")
}

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

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("POST", InnerDeviceCodeCheckEndpoint, nil)
	ginEng.ServeHTTP(w, req)

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

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

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

		assert.Equal(t, http.StatusBadRequest, w.Code)
		assert.Equal(t, "{\n    \"code\": 49900001,\n    \"message\": \"Bind Params Failed\"\n}", w.Body.String())
	})

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

		assert.Equal(t, http.StatusBadRequest, w.Code)
		assert.Equal(t, "{\n    \"code\": 49900001,\n    \"message\": \"Bind Params Failed\"\n}", w.Body.String())
	})

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

		assert.Equal(t, http.StatusBadRequest, w.Code)
		assert.Equal(t, "{\n    \"code\": 49900001,\n    \"message\": \"user_code is required\"\n}", w.Body.String())
	})
}

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

	mockOauth2Svc.EXPECT().HandleInnerDeviceCodeCheckRequest(gomock.Any(), "test_user_code").Return(
		nil, tierrs.NewNotFound("not found", errors.New("cause msg")))

	w := httptest.NewRecorder()
	var jsonStr = []byte(`{"user_code":"test_user_code"}`)
	req, _ := http.NewRequest("POST", InnerDeviceCodeCheckEndpoint, bytes.NewBuffer(jsonStr))
	setTestAuthHeader(req)
	req.Header.Set("Content-Type", "application/json")
	ginEng.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
	assert.Equal(t, "{\n    \"code\": 49900004,\n    \"message\": \"not found\"\n}", w.Body.String())
}

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

	mockSvcResponse := &oauth2_dto.InnerDeviceCheckResponse{
		InnerBasicResponse: oauth2_dto.InnerDefaultSuccessResponse(),
	}
	mockSvcResponse.Data.CreatedAt = "2024-01-25T20:41:51+08:00"
	mockSvcResponse.Data.IP = "127.0.0.1"
	mockSvcResponse.Data.UserAgent = "curl/7.64.1"
	mockOauth2Svc.EXPECT().HandleInnerDeviceCodeCheckRequest(gomock.Any(), "test_user_code").Return(mockSvcResponse, nil)

	w := httptest.NewRecorder()
	var jsonStr = []byte(`{"user_code":"test_user_code"}`)
	req, _ := http.NewRequest("POST", InnerDeviceCodeCheckEndpoint, bytes.NewBuffer(jsonStr))
	setTestAuthHeader(req)
	req.Header.Set("Content-Type", "application/json")
	ginEng.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "{\"code\":200,\"message\":\"ok\",\"data\":{\"ip\":\"127.0.0.1\",\"user_agent\":\"curl/7.64.1\",\"created_at\":\"2024-01-25T20:41:51+08:00\"}}", w.Body.String())
}

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

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("POST", InnerDeviceCodeConfirmationEndpoint, nil)
	ginEng.ServeHTTP(w, req)

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

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

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

		assert.Equal(t, http.StatusBadRequest, w.Code)
		assert.Equal(t, "{\n    \"code\": 49900001,\n    \"message\": \"Bind Params Failed\"\n}", w.Body.String())
	})

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

		assert.Equal(t, http.StatusBadRequest, w.Code)
		assert.Equal(t, "{\n    \"code\": 49900001,\n    \"message\": \"Bind Params Failed\"\n}", w.Body.String())
	})

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

		assert.Equal(t, http.StatusBadRequest, w.Code)
		assert.Equal(t, "{\n    \"code\": 49900001,\n    \"message\": \"user_code is required\"\n}", w.Body.String())
	})

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

		assert.Equal(t, http.StatusBadRequest, w.Code)
		assert.Equal(t, "{\n    \"code\": 49900001,\n    \"message\": \"action is required\"\n}", w.Body.String())
	})

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

		assert.Equal(t, http.StatusBadRequest, w.Code)
		assert.Equal(t, "{\n    \"code\": 49900001,\n    \"message\": \"action must be approve or deny\"\n}", w.Body.String())
	})

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

		assert.Equal(t, http.StatusBadRequest, w.Code)
		assert.Equal(t, "{\n    \"code\": 49900001,\n    \"message\": \"org_id is required when action is approve\"\n}", w.Body.String())
	})

	t.Run("InvalidOrgID", func(t *testing.T) {
		w := httptest.NewRecorder()
		var jsonStr = []byte(`{"user_code":"test_user_code","action":"approve", "org_id": "a"}`)
		req, _ := http.NewRequest("POST", InnerDeviceCodeConfirmationEndpoint, bytes.NewBuffer(jsonStr))
		setTestAuthHeader(req)
		req.Header.Set("Content-Type", "application/json")
		ginEng.ServeHTTP(w, req)

		assert.Equal(t, http.StatusBadRequest, w.Code)
		assert.Equal(t, "{\n    \"code\": 49900001,\n    \"message\": \"org_id is invalid\"\n}", w.Body.String())
	})
}

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

	mockOauth2Svc.EXPECT().HandleInnerDeviceCodeConfirmationRequest(
		gomock.Any(), gomock.Any(), "test_user_code", true, uint64(1),
	).Return(tierrs.NewNotFound("not found", errors.New("cause msg")))

	w := httptest.NewRecorder()
	var jsonStr = []byte(`{"user_code":"test_user_code","action":"approve","org_id": "1"}`)
	req, _ := http.NewRequest("POST", InnerDeviceCodeConfirmationEndpoint, bytes.NewBuffer(jsonStr))
	setTestAuthHeader(req)
	req.Header.Set("Content-Type", "application/json")
	ginEng.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
	assert.Equal(t, "{\n    \"code\": 49900004,\n    \"message\": \"not found\"\n}", w.Body.String())
}

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

	mockOauth2Svc.EXPECT().HandleInnerDeviceCodeConfirmationRequest(
		gomock.Any(), gomock.Any(), "test_user_code", true, uint64(1)).Return(nil)

	w := httptest.NewRecorder()
	var jsonStr = []byte(`{"user_code":"test_user_code","action":"approve","org_id": "1"}`)
	req, _ := http.NewRequest("POST", InnerDeviceCodeConfirmationEndpoint, bytes.NewBuffer(jsonStr))
	setTestAuthHeader(req)
	req.Header.Set("Content-Type", "application/json")
	ginEng.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "{\"code\":200,\"message\":\"ok\"}", w.Body.String())
}
