package api

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"

	mockdb "git.nochill.in/nochill/naice_pos/db/mock"
	db "git.nochill.in/nochill/naice_pos/db/sqlc"
	"git.nochill.in/nochill/naice_pos/util"
	"github.com/gin-gonic/gin"
	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/require"
)

type eqCreateUserMerchantParamsMatcher struct {
	arg      db.UserMerchantTxParams
	password string
}

func (e eqCreateUserMerchantParamsMatcher) Matches(x interface{}) bool {
	arg, ok := x.(db.UserMerchantTxParams)
	if !ok {
		return false
	}

	err := util.CheckPassword(e.password, arg.Password)
	if err != nil {
		return false
	}

	e.arg.Password = arg.Password
	return reflect.DeepEqual(e.arg.Password, arg.Password)
}

func (e eqCreateUserMerchantParamsMatcher) String() string {
	return fmt.Sprintf("matches arg  %v and password %v", e.arg, e.password)
}

func EqCreateUserMerchant(arg db.UserMerchantTxParams, password string) gomock.Matcher {
	return eqCreateUserMerchantParamsMatcher{arg, password}
}

func TestCreateUserMerchantAPI(t *testing.T) {
	userMerchant, password := randomUser(t)
	var userMerchantResult db.UserMerchantTxResult
	// var userMerchantResponse createUserMerchantResponse
	testCases := []struct {
		name          string
		body          gin.H
		buildStubs    func(store *mockdb.MockStore)
		checkResponse func(recorder *httptest.ResponseRecorder)
	}{
		{
			name: "OK",
			body: gin.H{
				"email":       userMerchant.Email,
				"fullname":    userMerchant.Fullname,
				"password":    password,
				"outlet_name": userMerchant.OutletName,
			},
			buildStubs: func(store *mockdb.MockStore) {
				arg := db.UserMerchantTxParams{
					Email:      userMerchant.Email,
					Fullname:   userMerchant.Fullname,
					OutletName: userMerchant.OutletName,
				}
				store.EXPECT().
					CreateUserMerchantTx(gomock.Any(), EqCreateUserMerchant(arg, password)).
					Times(1).
					Return(userMerchantResult, nil)
			},
			checkResponse: func(recorder *httptest.ResponseRecorder) {
				require.Equal(t, http.StatusOK, recorder.Code)
				// requireBodyMatchUserMerchant(t, recorder.Body, userMerchantResponse)
			},
		},
	}

	for i := range testCases {
		tc := testCases[i]

		t.Run(tc.name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			defer ctrl.Finish()

			store := mockdb.NewMockStore(ctrl)
			tc.buildStubs(store)

			server := newTestServer(t, store)
			recorder := httptest.NewRecorder()

			data, err := json.Marshal(tc.body)
			require.NoError(t, err)

			url := "/user/merchants"
			request, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
			require.NoError(t, err)

			server.router.ServeHTTP(recorder, request)
			tc.checkResponse(recorder)

		})
	}
}

func TestUserLoginAPI(t *testing.T) {
	user, password := randomUser(t)
	var userProfile db.User
	var userOutletProfile db.Merchant

	testCases := []struct {
		name          string
		body          gin.H
		buildStubs    func(store *mockdb.MockStore)
		checkResponse func(recorder *httptest.ResponseRecorder)
	}{
		{
			name: "OK",
			body: gin.H{
				"email":    user.Email,
				"password": password,
			},
			buildStubs: func(store *mockdb.MockStore) {
				store.EXPECT().
					GetUserByEmail(gomock.Any(), gomock.Eq(user.Email)).
					Times(1).
					Return(userProfile, nil)
				store.EXPECT().
					GetMerchantByUserId(gomock.Any(), gomock.Any()).
					Times(1).
					Return(userOutletProfile, nil)
				store.EXPECT().
					CreateSession(gomock.Any(), gomock.Any()).
					Times(1)
			},
			checkResponse: func(recorder *httptest.ResponseRecorder) {
				require.Equal(t, http.StatusOK, recorder.Code)
			},
		},
	}

	for i := range testCases {
		tc := testCases[i]

		t.Run(tc.name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			defer ctrl.Finish()

			store := mockdb.NewMockStore(ctrl)
			tc.buildStubs(store)

			server := newTestServer(t, store)
			recorder := httptest.NewRecorder()

			data, err := json.Marshal(tc.body)
			require.NoError(t, err)

			url := "/user/login"
			request, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
			require.NoError(t, err)

			server.router.ServeHTTP(recorder, request)
			tc.checkResponse(recorder)
		})
	}
}

func randomUser(t *testing.T) (userMerchant db.UserMerchantTxParams, password string) {
	password = util.RandomString(6)
	hashedPassword, err := util.HashPassword(password)
	require.NoError(t, err)

	userMerchant = db.UserMerchantTxParams{
		Email:      util.RandomEmail(),
		Fullname:   util.RandomString(5),
		Password:   hashedPassword,
		OutletName: util.RandomString(10),
	}

	return
}

func requireBodyMatchUserMerchant(t *testing.T, body *bytes.Buffer, userMerchant userMerchantResponse) {
	data, err := ioutil.ReadAll(body)
	require.NoError(t, err)

	var gotUserMerchant db.UserMerchantTxParams
	err = json.Unmarshal(data, &gotUserMerchant)

	require.NoError(t, err)
}