package handlers_test

import (
	"encoding/json"
	"fmt"
	"net/http"
	"testing"

	"github.com/fiskerinc/cloud-services/pkg/redis"

	"otaupdate/handlers"
	"otaupdate/services"

	m "github.com/fiskerinc/cloud-services/pkg/common"
	orm "github.com/fiskerinc/cloud-services/pkg/db/queries"
	mo "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
	r "github.com/fiskerinc/cloud-services/pkg/redis"
	rm "github.com/fiskerinc/cloud-services/pkg/redis/tester"
	th "github.com/fiskerinc/cloud-services/pkg/testhelper"
	"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
)

type MockCarsCounted struct {
	mo.MockCars
	searchCounter int
	countCounter  int
}

func (m *MockCarsCounted) Search(s *m.CarSearch, q *orm.PageQueryOptions) ([]m.Car, error) {
	m.searchCounter++
	return m.MockCars.Search(s, q)
}

func (m *MockCarsCounted) SearchCount(s *m.CarSearch) (int, error) {
	m.countCounter++
	return m.MockCars.SearchCount(s)
}

func TestGetVehiclesCached(t *testing.T) {
	r.MockRedisConnection()
	mockRedis := rm.MockRedis{
		GetSetResults: "[]",
		GetCommandResult: map[string]map[string]interface{}{
			"SMEMBERS": {redis.CarSessionsKey(): []interface{}{}},
		},
	}
	services.SetRedisClientPool(rm.NewMockClientPool(&mockRedis))
	mock := MockCarsCounted{}
	services.GetDB().SetCars(&mock)
	defaultOrder := "vin"

	tests := []mo.DBHttpTest{
		{
			Name:             "Default Limit 100",
			Request:          th.MakeTestRequest(http.MethodGet, "http://example.com/cars", nil),
			ExpectedStatus:   http.StatusOK,
			ExpectedResponse: longListExpectedResultGen(longListData, 0, 0),
			DBTestCase: mo.DBTestCase{
				ExpectedFilter: m.CarSearch{},
				ExpectedPage: &orm.PageQueryOptions{
					Order:  defaultOrder,
					Limit:  100,
					Offset: 0,
				},
				MockListResponse: longListData[:100],
			},
		},
		{
			Name:             "Cached Limit 100",
			Request:          th.MakeTestRequest(http.MethodGet, "http://example.com/cars", nil),
			ExpectedStatus:   http.StatusOK,
			ExpectedResponse: longListExpectedResultGen(longListData, 0, 0),
			DBTestCase: mo.DBTestCase{
				ExpectedFilter: m.CarSearch{},
				ExpectedPage: &orm.PageQueryOptions{
					Order:  defaultOrder,
					Limit:  100,
					Offset: 0,
				},
				MockListResponse: longListData[:100],
			},
		},
	}

	mo.RunDBTests(t, tests, handlers.HandleVehiclesGet, &mock)

	if mock.searchCounter != 2 && mock.countCounter != 2 {
		t.Errorf("Expected search and count to be called only once, got %d and %d", mock.searchCounter, mock.countCounter)
	}
}

func TestGetVehicles(t *testing.T) {
	r.MockRedisConnection()
	mockRedis := rm.MockRedis{
		GetSetResults: "[]",
		GetCommandResult: map[string]map[string]interface{}{
			"SMEMBERS": {redis.CarSessionsKey(): []interface{}{}},
		},
	}
	services.SetRedisClientPool(rm.NewMockClientPool(&mockRedis))
	handlers.SetVehiclesCache(&rm.MockVehiclesCache{})
	mock := mo.MockCars{}
	services.GetDB().SetCars(&mock)
	expectedResp := `{"data":[{"vin":"3C4PDCBG0ET127145","year":2021,"model":"Ocean"}],"total":1}`
	expectedRespNoTotal := `{"data":[{"vin":"3C4PDCBG0ET127145","year":2021,"model":"Ocean"}]}`
	defaultOrder := "vin"
	listData := []m.Car{
		{
			VIN:   "3C4PDCBG0ET127145",
			Model: "Ocean",
			Year:  2021,
		},
	}
	tests := []mo.DBHttpTest{
		{
			Name:             "No parameters",
			Request:          th.MakeTestRequest(http.MethodGet, "http://example.com/cars", nil),
			ExpectedStatus:   http.StatusOK,
			ExpectedResponse: expectedResp,
			DBTestCase: mo.DBTestCase{
				ExpectedFilter: m.CarSearch{},
				ExpectedPage: &orm.PageQueryOptions{
					Order:  defaultOrder,
					Limit:  100,
					Offset: 0,
				},
				MockListResponse: listData,
			},
		},
		{
			Name:             "Default Limit 100",
			Request:          th.MakeTestRequest(http.MethodGet, "http://example.com/cars", nil),
			ExpectedStatus:   http.StatusOK,
			ExpectedResponse: longListExpectedResultGen(longListData, 0, 0),
			DBTestCase: mo.DBTestCase{
				ExpectedFilter: m.CarSearch{},
				ExpectedPage: &orm.PageQueryOptions{
					Order:  defaultOrder,
					Limit:  100,
					Offset: 0,
				},
				MockListResponse: longListData[:100],
			},
		},
		{
			Name:             "Wrong limit, -100",
			Request:          th.MakeTestRequest(http.MethodGet, "http://example.com/cars?limit=-100", nil),
			ExpectedStatus:   http.StatusBadRequest,
			ExpectedResponse: `{"message":"Limit less than 0","error":"Bad Request"}`,
		},
		{
			Name:             "Wrong limit, 1000",
			Request:          th.MakeTestRequest(http.MethodGet, "http://example.com/cars?limit=1000", nil),
			ExpectedStatus:   http.StatusBadRequest,
			ExpectedResponse: `{"message":"Limit greater than 100","error":"Bad Request"}`,
		},
		{
			Name:             "Invalid VIN",
			Request:          th.MakeTestRequest(http.MethodGet, "http://example.com/cars?vin=FISKER123", nil),
			ExpectedStatus:   http.StatusBadRequest,
			ExpectedResponse: `{"message":"VIN 'FISKER123' invalid","error":"Bad Request"}`,
		},
		{
			Name:             "Id parameter",
			Request:          th.MakeTestRequest(http.MethodGet, "http://example.com/cars?vin=3C4PDCBG0ET127145", nil),
			ExpectedStatus:   http.StatusOK,
			ExpectedResponse: expectedRespNoTotal,
			DBTestCase: mo.DBTestCase{
				ExpectedFilter: m.CarSearch{
					Car: m.Car{
						VIN: "3C4PDCBG0ET127145",
					},
				},
				ExpectedPage: &orm.PageQueryOptions{
					Order:  defaultOrder,
					Limit:  100,
					Offset: 0,
				},
				MockListResponse: listData,
			},
		},
		{
			Name:             "Name, version, description parameters",
			Request:          th.MakeTestRequest(http.MethodGet, "http://example.com/cars?model=Ocean&year=2021", nil),
			ExpectedStatus:   http.StatusOK,
			ExpectedResponse: expectedResp,
			DBTestCase: mo.DBTestCase{
				ExpectedFilter: m.CarSearch{
					Car: m.Car{
						Model: "Ocean",
						Year:  2021,
					},
				},
				ExpectedPage: &orm.PageQueryOptions{
					Order:  defaultOrder,
					Limit:  100,
					Offset: 0,
				},
				MockListResponse: listData,
			},
		},
		{
			Name:             "Paging parameters",
			Request:          th.MakeTestRequest(http.MethodGet, "http://example.com/cars?offset=10&limit=5", nil),
			ExpectedStatus:   http.StatusOK,
			ExpectedResponse: expectedRespNoTotal,
			DBTestCase: mo.DBTestCase{
				ExpectedFilter: m.CarSearch{},
				ExpectedPage: &orm.PageQueryOptions{
					Order:  defaultOrder,
					Limit:  5,
					Offset: 10,
				},
				MockListResponse: listData,
			},
		},
		{
			// TODO: since the logic is on the db layer, we can't check the online/offline test here,
			// it's whether we have to have a full mock database, or we have to expect the query to be
			// equal to some string.
			Name:             "Online",
			Request:          th.MakeTestRequest(http.MethodGet, "http://example.com/cars?online=true", nil),
			ExpectedStatus:   http.StatusOK,
			ExpectedResponse: expectedResp,
			DBTestCase: mo.DBTestCase{
				ExpectedFilter: m.CarSearch{
					Online: &m.CarOnlineFilter{
						Online: elptr.ElPtr(true),
					},
				},
				ExpectedPage: &orm.PageQueryOptions{
					Order:  defaultOrder,
					Limit:  100,
					Offset: 0,
				},
				MockListResponse: listData,
			},
		},
		{
			Name:             "Offline",
			Request:          th.MakeTestRequest(http.MethodGet, "http://example.com/cars?online=false", nil),
			ExpectedStatus:   http.StatusOK,
			ExpectedResponse: expectedResp,
			DBTestCase: mo.DBTestCase{
				ExpectedFilter: m.CarSearch{
					Online: &m.CarOnlineFilter{
						Online: elptr.ElPtr(false),
					},
				},
				ExpectedPage: &orm.PageQueryOptions{
					Order:  defaultOrder,
					Limit:  100,
					Offset: 0,
				},
				MockListResponse: listData,
			},
		},
		{
			Name:             "Multiple VINs",
			Request:          th.MakeTestRequest(http.MethodGet, "http://example.com/cars?vins=SCAZD42A3HCX11779,SCAZD42A3HCX11850", nil),
			ExpectedStatus:   http.StatusOK,
			ExpectedResponse: `{"data":[{"vin":"SCAZD42A3HCX11779","year":2021,"model":"Ocean"},{"vin":"SCAZD42A3HCX11850","year":2021,"model":"Ocean"}],"total":2}`,
			DBTestCase: mo.DBTestCase{
				ExpectedFilter: m.CarSearch{
					VINs: "SCAZD42A3HCX11779,SCAZD42A3HCX11850",
				},
				ExpectedPage: &orm.PageQueryOptions{
					Order:  defaultOrder,
					Limit:  100,
					Offset: 0,
				},
				MockListResponse: longListData[:2],
			},
		},
		{
			Name:             "Error",
			Request:          th.MakeTestRequest(http.MethodGet, "http://example.com/cars", nil),
			ExpectedStatus:   http.StatusServiceUnavailable,
			ExpectedResponse: `{"message":"something went wrong","error":"Service Unavailable"}`,
			DBTestCase: mo.DBTestCase{
				ExpectedFilter: m.CarSearch{},
				ExpectedPage: &orm.PageQueryOptions{
					Order:  defaultOrder,
					Limit:  100,
					Offset: 0,
				},
				MockError: fmt.Errorf("something went wrong"),
			},
		},
	}

	mo.RunDBTests(t, tests, handlers.HandleVehiclesGet, &mock)
}

// 105 entries
var longListData []m.Car = []m.Car{
	{
		VIN:   "SCAZD42A3HCX11779",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX11850",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX16041",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX12984",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX11576",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX15688",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX17778",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX10518",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX17196",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX17156",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX13043",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX13784",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX19371",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX19905",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX18835",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX11056",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX16812",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX13636",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX12874",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX17360",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX13373",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX11933",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX11286",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX16703",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX12358",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX15157",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX17005",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX10283",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX11247",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX17714",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX16219",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX16036",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX19125",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX15079",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX18843",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX14722",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX16906",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX11601",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX14453",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX11028",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX17184",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX12328",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX13860",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX15042",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX17519",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX10309",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX10304",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX17052",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX19086",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX15606",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX19217",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX16092",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX17281",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX13886",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX19772",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX15135",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX17306",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX16200",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX12910",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX10944",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX10117",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX16097",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX13145",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX19594",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX10200",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX14353",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX11049",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX10682",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX14547",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX14368",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX12021",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX17152",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX19291",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX11513",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX13697",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX18883",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX14454",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX16767",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX14896",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX13477",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX14916",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX13866",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX17261",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX15298",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX17098",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX19128",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX15130",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX10156",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX19665",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX10477",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX18530",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX18158",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX14232",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX13238",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX17683",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX11460",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX11272",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX14306",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX17241",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX13865",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX12546",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX19404",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX19747",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX13450",
		Model: "Ocean",
		Year:  2021,
	},
	{
		VIN:   "SCAZD42A3HCX19459",
		Model: "Ocean",
		Year:  2021,
	},
}

type fakeResponse struct {
	Data  []m.Car `json:"data"`
	Total int     `json:"total"`
}

func longListExpectedResultGen(carList []m.Car, offset, limit int) string {
	max := len(carList)
	if limit <= 0 {
		limit = 100
	}
	if offset+limit < max {
		max = offset + limit
	}
	carSubset := carList[offset:max]
	fakeRes := fakeResponse{Data: carSubset, Total: len(carSubset)}

	fmt.Println()
	b, _ := json.Marshal(fakeRes)
	return string(b)
}
