package handlers_test

import (
	"context"
	"encoding/base64"
	"fmt"
	"net/http"
	"testing"

	"otaupdate/handlers"
	"otaupdate/services"

	"github.com/fiskerinc/cloud-services/pkg/common"
	dbm "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks"
	"github.com/fiskerinc/cloud-services/pkg/grpc/kafka_grpc"
	htc "github.com/fiskerinc/cloud-services/pkg/httpclient/tester"
	"github.com/fiskerinc/cloud-services/pkg/httphandlers"
	"github.com/fiskerinc/cloud-services/pkg/kafka"
	km "github.com/fiskerinc/cloud-services/pkg/kafka/mock"
	"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/testrunner"
	uhelpers "github.com/fiskerinc/cloud-services/pkg/usecase_helpers"
	"github.com/fiskerinc/cloud-services/pkg/utils/whereami"
	"github.com/fiskerinc/cloud-services/pkg/validator"
	"github.com/go-pg/pg/v10"
	"google.golang.org/protobuf/proto"
)

func TestCarUpdateAdd(t *testing.T) {
	whereami.SetService(whereami.OTA)
	redis.MockRedisConnection()
	mockDB := dbm.MockCarUpdates{}
	mockKafka := km.KafkaMock{}
	mockRedis := rm.MockRedis{}
	vin := "1G1FP87S3GN100062"
	mockFoa := FoaServiceMock{}
	services.SetFoaService(&mockFoa)
	services.GetDB().SetCarUpdates(&mockDB)
	services.SetKafkaProducer(&mockKafka)
	services.SetRedisClientPool(rm.NewMockClientPool(&mockRedis))
	otaUpdateKey := common.Service.Key(vin)
	attendentTopic := kafka.AttendantServiceGRPCKafka
	updateMsg := &kafka_grpc.GRPC_AttendantPayload_UpdateManifest{
		UpdateManifest: &kafka_grpc.UpdateManifest{
			CarUpdateId: 1,
		},
	}
	kafkaMSG := kafka_grpc.GRPC_AttendantPayload{
		Handler: "send_manifest",
		Data:    updateMsg,
	}
	binaryPayload, _ := proto.Marshal(&kafkaMSG)
	validMessage := fmt.Sprintf(`"%s"`, base64.StdEncoding.EncodeToString(binaryPayload))
	standardManifest := common.UpdateManifest{
		ID:         100,
		Name:       "TEST",
		Version:    "10000",
		Type:       "standard",
		Country:    "US",
		PowerTrain: "MD23",
		Restraint:  "None",
		Model:      "Ocean",
		Trim:       "Sport",
		Year:       2022,
		BodyType:   "truck",
		ECUs: []*common.UpdateManifestECU{
			{
				ECU:          "ICC",
				Version:      "SWVERSION",
				HWVersions:   []string{"HWVERSION"},
				Mode:         "ICC",
				SelfDownload: true,
				Files: []*common.UpdateManifestFile{
					{
						FileID:        "AAAAAAA",
						URL:           "http://download.com/file1.bin",
						FileSize:      1000,
						Checksum:      "AAAAAAA",
						WriteRegionID: 100,
						WriteRegion: common.MemoryRegion{
							Offset: 101,
							Length: 102,
						},
						EraseRegionID: 200,
						EraseRegion: &common.MemoryRegion{
							Offset: 201,
							Length: 202,
						},
					},
				},
			},
			{
				ECU:        "ECU",
				Version:    "SWVERSION",
				HWVersions: []string{"HWVERSION"},
				Mode:       "D",
				Files: []*common.UpdateManifestFile{
					{
						FileID:        "BBBBBBB",
						URL:           "http://download.com/file2.bin",
						FileSize:      2000,
						Checksum:      "BBBBBBB",
						WriteRegionID: 300,
						WriteRegion: common.MemoryRegion{
							Offset: 301,
							Length: 302,
						},
						EraseRegionID: 400,
						EraseRegion: &common.MemoryRegion{
							Offset: 401,
							Length: 402,
						},
					},
				},
			},
		},
	}
	forcedManifest := standardManifest
	forcedManifest.Type = "forced"

	tests := []testrunner.TestCase{
		{
			Name: "Bad car ids",
			HttpTestCase: &htc.HttpTestCase{
				Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{
					UpdateManifestID: 1,
				}),
				ExpectedStatus:   http.StatusBadRequest,
				ExpectedResponse: `{"message":"VINs required","error":"Bad Request"}`,
			},
		},
		{
			Name: "No data",
			HttpTestCase: &htc.HttpTestCase{
				Request:          th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", nil),
				ExpectedStatus:   http.StatusBadRequest,
				ExpectedResponse: `{"message":"UpdateManifestID required. VINs required","error":"Bad Request"}`,
			},
		},
		{
			Name: "Bad package ids",
			HttpTestCase: &htc.HttpTestCase{
				Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{
					VINs: []string{"NONEXISTENT", "NONEXISTENT2"},
				}),
				ExpectedStatus:   http.StatusBadRequest,
				ExpectedResponse: `{"message":"UpdateManifestID required. VINs[0] 'NONEXISTENT' invalid. VINs[1] 'NONEXISTENT2' invalid","error":"Bad Request"}`,
			},
		},
		{
			Name: "Good data standard manifest id",
			HttpTestCase: &htc.HttpTestCase{
				Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{
					UpdateManifestID: 1,
					VINs:             []string{"1G1FP87S3GN100062"},
				}),
				ExpectedStatus:   http.StatusOK,
				ExpectedResponse: `{"data":[{"id":1,"vin":"1G1FP87S3GN100062","manifest_id":1,"status":"pending","username":"testusername","UpdateSource":"OTA"}]}`,
			},
			DBTestCase: &dbm.DBTestCase{
				SetupMockResponse: func() {
					services.GetDB().SetUpdateManifests(&dbm.MockUpdateManifests{
						LoadResponse: &standardManifest,
					})
				},
			},
			RedisTestCase: &rm.RedisTestCase{},
			KafkaTestCase: &km.KafkaTestCase{
				ExpectedProduceMessages: map[string]map[string]interface{}{
					attendentTopic: {
						otaUpdateKey: validMessage,
					},
				},
			},
		},
		{
			Name: "Case where manifest id does not exist",
			HttpTestCase: &htc.HttpTestCase{
				Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{
					UpdateManifestID: 1,
					VINs:             []string{"1G1FP87S3GN100062"},
				}),
				ExpectedStatus:   http.StatusNotFound,
				ExpectedResponse: `{"message":"pg: no rows in result set","error":"Not Found"}`,
				Setup: func() {
					services.GetDB().SetUpdateManifests(&dbm.MockUpdateManifests{
						LoadResponse: nil,
						DBMockHelper: dbm.DBMockHelper{
							Error: pg.ErrNoRows,
						},
					})
				},
			},
		},
		{
			Name: "Good data forced manifest id",
			HttpTestCase: &htc.HttpTestCase{
				Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{
					UpdateManifestID: 1,
					VINs:             []string{"1G1FP87S3GN100062"},
				}),
				ExpectedStatus:   http.StatusOK,
				ExpectedResponse: `{"data":[{"id":1,"vin":"1G1FP87S3GN100062","manifest_id":1,"status":"pending","username":"testusername","UpdateSource":"OTA"}]}`,
			},
			DBTestCase: &dbm.DBTestCase{
				SetupMockResponse: func() {
					services.GetDB().SetUpdateManifests(&dbm.MockUpdateManifests{
						LoadResponse: &forcedManifest,
					})
				},
			},
			RedisTestCase: &rm.RedisTestCase{},
			KafkaTestCase: &km.KafkaTestCase{
				ExpectedProduceMessages: map[string]map[string]interface{}{
					attendentTopic: {
						otaUpdateKey: validMessage,
					},
				},
			},
		},
		{
			Name: "Error",
			HttpTestCase: &htc.HttpTestCase{
				Request: th.MakeTestRequest(http.MethodPost, "http://example.com/carupdate", uhelpers.JSONCarUpdatesRequest{
					UpdateManifestID: 1,
					VINs:             []string{"1G1FP87S3GN100062"},
				}),
				ExpectedStatus:   http.StatusServiceUnavailable,
				ExpectedResponse: `{"message":"something went wrong","error":"Service Unavailable"}`,
			},
			DBTestCase: &dbm.DBTestCase{
				MockError: fmt.Errorf("something went wrong"),
			},
		},
	}

	for _, test := range tests {
		mockRedis.Reset()
		mockKafka.Reset()

		if test.DBTestCase != nil {
			test.DBTestCase.SetupDB(&mockDB)
		}
		if test.RedisTestCase != nil {
			test.RedisTestCase.SetupRedis(&mockRedis)
		}
		if test.KafkaTestCase != nil {
			test.KafkaTestCase.Setup(&mockKafka)
		}

		if test.HttpTestCase != nil {
			if test.HttpTestCase.Setup != nil {
				test.HttpTestCase.Setup()
			}

			ctx := context.WithValue(test.HttpTestCase.Request.Context(), httphandlers.ClientIDContextKey, "testusername")
			test.HttpTestCase.Request = test.HttpTestCase.Request.WithContext(ctx)

			w := test.HttpTestCase.Test(handlers.HandleCarUpdatesAdd)
			test.HttpTestCase.ValidateHttp(t, test.Name, w)
		}

		if test.DBTestCase != nil {
			test.DBTestCase.Validate(t, test.Name, &mockDB)
		}
		if test.RedisTestCase != nil {
			test.RedisTestCase.Validate(t, test.Name, &mockRedis)
		}
		if test.KafkaTestCase != nil {
			test.KafkaTestCase.Validate(t, test.Name, &mockKafka)
		}
	}
}

func TestJSONCarUpdatesRequestValidation(t *testing.T) {
	request := uhelpers.JSONCarUpdatesRequest{
		UpdateManifestID: 1,
		VINs:             []string{},
	}

	err := validator.ValidateStruct(request)
	if err == nil {
		t.Errorf(th.TestErrorTemplate, "Validate VINs count", nil, err)
	} else {
		_, msg := validator.GetValidationErrorMsg(err)
		expected := "VINs less than 1"
		if msg != expected {
			t.Errorf(th.TestErrorTemplate, "Validate VINs count", expected, msg)
		}
	}

	request.VINs = []string{"1G1FP87S3GN100062"}
	err = validator.ValidateStruct(request)
	if err != nil {
		t.Errorf(th.TestErrorTemplate, "Validate Good VIN", nil, err)
	}

	request.VINs = []string{"1G1FP87S3GN10006I"}
	err = validator.ValidateStruct(request)
	if err == nil {
		t.Errorf(th.TestErrorTemplate, "Validate Bad VIN", nil, err)
	} else {
		_, msg := validator.GetValidationErrorMsg(err)
		expected := "VINs[0] '1G1FP87S3GN10006I' invalid"
		if msg != expected {
			t.Errorf(th.TestErrorTemplate, "Validate Bad VIN", expected, msg)
		}
	}
}

type FoaServiceMock struct{}

func (f *FoaServiceMock) OtaUpdateStatus(vin string, carUpdate *common.CarUpdate, status *common.CarUpdateProgress) (*http.Response, error) {
	return &http.Response{StatusCode: 200}, nil
}
