package handlers

import (
	"context"
	"net/http"
	"otaupdate/services"
	"testing"

	"github.com/fiskerinc/cloud-services/pkg/grpc/sms"
	"github.com/fiskerinc/cloud-services/pkg/httpclient/tester"
	th "github.com/fiskerinc/cloud-services/pkg/testhelper"
	"github.com/pkg/errors"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type MockSmsClientSucc struct{}

// HandleSMSQueue implements sms.SMSServiceClient.
func (*MockSmsClientSucc) HandleSMSQueue(ctx context.Context, in *sms.SendSMSRequest, opts ...grpc.CallOption) (*sms.SMSQueueResponse, error) {
	return &sms.SMSQueueResponse{
		SmsMsgID:       "1023",
		SentSuccessful: false,
	}, nil
}

func (m *MockSmsClientSucc) HandleSMSSend(ctx context.Context,in *sms.SendSMSRequest,opts ...grpc.CallOption,) (*sms.SMSDetailsResponse, error) {
	return &sms.SMSDetailsResponse{
		SmsMsgID:     "123",
		Status:       5,
		MessageText:  "messageText",
		SenderLogin:  "sender",
		SentFrom:     "from",
		SentTo:       "to",
		DateSent:     "dateSent",
		MsgType:      "type",
		DateModified: "dateModified",
		ICCID:        "iccid",
	}, nil
}

func (m *MockSmsClientSucc) HandleGetProducts(ctx context.Context, in *sms.GetAvailableProductsRequest, opts ...grpc.CallOption) (*sms.GetAvailableProductsResponse, error) {
	return nil, nil
}
func (m *MockSmsClientSucc) HandleChangeRatePlan(ctx context.Context, in *sms.ChangeRatePlanRequest, opts ...grpc.CallOption) (*sms.ChangeRatePlanResponse, error) {
	return nil, nil
}
func (m *MockSmsClientSucc) HandleCustomAttributes(ctx context.Context, in *sms.CustomAtributesRequest, opts ...grpc.CallOption) (*sms.CustomAtributeResponse, error) {
	return nil, nil
}
func (m *MockSmsClientSucc) HandleDeviceDetails(ctx context.Context, in *sms.DeviceDetailsRequest, opts ...grpc.CallOption) (*sms.DeviceDetailsResponse, error) {
	return nil, nil
}

type MockSmsClientFail struct{}

// HandleSMSQueue implements sms.SMSServiceClient.
func (*MockSmsClientFail) HandleSMSQueue(ctx context.Context, in *sms.SendSMSRequest, opts ...grpc.CallOption) (*sms.SMSQueueResponse, error) {
	return &sms.SMSQueueResponse{
		SmsMsgID:       "1023",
		SentSuccessful: false,
	}, nil
}

func (m *MockSmsClientFail) HandleSMSSend(
	ctx context.Context,
	in *sms.SendSMSRequest,
	opts ...grpc.CallOption,
) (*sms.SMSDetailsResponse, error) {
	return nil, errors.New("unintelligable error")
}

func (m *MockSmsClientFail) HandleGetProducts(ctx context.Context, in *sms.GetAvailableProductsRequest, opts ...grpc.CallOption) (*sms.GetAvailableProductsResponse, error) {
	return nil, nil
}
func (m *MockSmsClientFail) HandleChangeRatePlan(ctx context.Context, in *sms.ChangeRatePlanRequest, opts ...grpc.CallOption) (*sms.ChangeRatePlanResponse, error) {
	return nil, nil
}
func (m *MockSmsClientFail) HandleCustomAttributes(ctx context.Context, in *sms.CustomAtributesRequest, opts ...grpc.CallOption) (*sms.CustomAtributeResponse, error) {
	return nil, nil
}
func (m *MockSmsClientFail) HandleDeviceDetails(ctx context.Context, in *sms.DeviceDetailsRequest, opts ...grpc.CallOption) (*sms.DeviceDetailsResponse, error) {
	return nil, nil
}

type MockSmsClientFailWithGoodErr struct{}

// HandleSMSQueue implements sms.SMSServiceClient.
func (*MockSmsClientFailWithGoodErr) HandleSMSQueue(ctx context.Context, in *sms.SendSMSRequest, opts ...grpc.CallOption) (*sms.SMSQueueResponse, error) {
	return &sms.SMSQueueResponse{
		SmsMsgID:       "1023",
		SentSuccessful: false,
	}, nil
}

func (m *MockSmsClientFailWithGoodErr) HandleSMSSend(
	ctx context.Context,
	in *sms.SendSMSRequest,
	opts ...grpc.CallOption,
) (*sms.SMSDetailsResponse, error) {
	return nil, status.Error(codes.Internal, "bad message status")
}

func (m *MockSmsClientFailWithGoodErr) HandleGetProducts(ctx context.Context, in *sms.GetAvailableProductsRequest, opts ...grpc.CallOption) (*sms.GetAvailableProductsResponse, error) {
	return nil, nil
}
func (m *MockSmsClientFailWithGoodErr) HandleChangeRatePlan(ctx context.Context, in *sms.ChangeRatePlanRequest, opts ...grpc.CallOption) (*sms.ChangeRatePlanResponse, error) {
	return nil, nil
}
func (m *MockSmsClientFailWithGoodErr) HandleCustomAttributes(ctx context.Context, in *sms.CustomAtributesRequest, opts ...grpc.CallOption) (*sms.CustomAtributeResponse, error) {
	return nil, nil
}
func (m *MockSmsClientFailWithGoodErr) HandleDeviceDetails(ctx context.Context, in *sms.DeviceDetailsRequest, opts ...grpc.CallOption) (*sms.DeviceDetailsResponse, error) {
	return nil, nil
}

func Test_HandleSMSSend(t *testing.T) {
	type testCase struct {
		name     string
		doBefore func()
		httpTC   tester.HttpTestCase
	}

	testCases := []testCase{
		{
			name:     "Valid",
			doBefore: func() { services.SetSmsClient(&MockSmsClientSucc{}) },
			httpTC: tester.HttpTestCase{
				Request: th.MakeTestRequest(
					"POST",
					"http://example.com/sms",
					sms.SendSMSRequest{ICCID: "iccid", MessageText: "messageText"}),
				ExpectedStatus:   http.StatusOK,
				ExpectedRegexMap: map[string]string{"smsMsgID": "123", "status": "Delivered", "messageText": "messageText", "senderLogin": "sender", "sentTo": "to", "sentFrom": "from", "msgType": "type", "dateSent": "dateSent", "dateModified": "dateModified", "ICCID": "iccid"},
			},
		},
		{
			name:     "InvalidJson",
			doBefore: func() { services.SetSmsClient(&MockSmsClientSucc{}) },
			httpTC: tester.HttpTestCase{
				Request: th.MakeTestRequest(
					"POST",
					"http://example.com/sms",
					"invalid"),
				ExpectedStatus:   http.StatusBadRequest,
				ExpectedResponse: `{"message":"json: cannot unmarshal string into Go value of type sms.SendSMSRequest","error":"Bad Request"}`,
			},
		},
		{
			name:     "Failing with bad error",
			doBefore: func() { services.SetSmsClient(&MockSmsClientFail{}) },
			httpTC: tester.HttpTestCase{
				Request: th.MakeTestRequest(
					"POST",
					"http://example.com/sms",
					sms.SendSMSRequest{ICCID: "iccid", MessageText: "messageText"}),
				ExpectedStatus:   http.StatusInternalServerError,
				ExpectedResponse: `{"message":"unintelligable error","error":"Internal Server Error"}`,
			},
		},
		{
			name:     "Failing with good error",
			doBefore: func() { services.SetSmsClient(&MockSmsClientFailWithGoodErr{}) },
			httpTC: tester.HttpTestCase{
				Request: th.MakeTestRequest(
					"POST",
					"http://example.com/sms",
					sms.SendSMSRequest{ICCID: "iccid", MessageText: "messageText"}),
				ExpectedStatus:   http.StatusExpectationFailed,
				ExpectedResponse: `{"message":"bad message status","error":"Expectation Failed"}`,
			},
		},
	}

	for _, tc := range testCases {
		tc.doBefore()
		w := tc.httpTC.Test(HandleSMSSend)
		tc.httpTC.ValidateHttp(t, tc.name, w)
	}
}

func Test_grpcErrToHttpErr(t *testing.T) {
	type args struct {
		status *status.Status
	}

	tests := []struct {
		name     string
		args     args
		wantCode int
		wantMsg  string
	}{
		{
			name: "Internal",
			args: args{
				status: status.New(codes.Internal, "just"),
			},
			wantCode: 500,
			wantMsg:  "just",
		},
		{
			name: "Internal with bad status",
			args: args{
				status: status.New(codes.Internal, "bad message status"),
			},
			wantCode: http.StatusExpectationFailed,
			wantMsg:  "bad message status",
		},

		{
			name: "InvalidArgument",
			args: args{
				status: status.New(codes.InvalidArgument, "just"),
			},
			wantCode: 400,
			wantMsg:  "just",
		},
		{
			name: "NotFound",
			args: args{
				status: status.New(codes.NotFound, "just"),
			},
			wantCode: 404,
			wantMsg:  "just",
		},
		{
			name: "AlreadyExists",
			args: args{
				status: status.New(codes.AlreadyExists, "just"),
			},
			wantCode: 409,
			wantMsg:  "just",
		},
		{
			name: "PermissionDenied",
			args: args{
				status: status.New(codes.PermissionDenied, "just"),
			},
			wantCode: 403,
			wantMsg:  "just",
		},
		{
			name: "Unauthenticated",
			args: args{
				status: status.New(codes.Unauthenticated, "just"),
			},
			wantCode: 401,
			wantMsg:  "just",
		},
		{
			name: "Unimplemented",
			args: args{
				status: status.New(codes.Unimplemented, "just"),
			},
			wantCode: 501,
			wantMsg:  "just",
		},
		{
			name: "Unknown",
			args: args{
				status: status.New(codes.Unknown, "just"),
			},
			wantCode: 500,
			wantMsg:  "just",
		},
		{
			name: "Unavailable",
			args: args{
				status: status.New(codes.Unavailable, "just"),
			},
			wantCode: 503,
			wantMsg:  "just",
		},
		{
			name: "DeadlineExceeded",
			args: args{
				status: status.New(codes.DeadlineExceeded, "just"),
			},
			wantCode: http.StatusRequestTimeout,
			wantMsg:  "just",
		},
		{
			name: "Canceled",
			args: args{
				status: status.New(codes.Canceled, "just"),
			},
			wantCode: 499,
			wantMsg:  "just",
		},
		{
			name: "FailedPrecondition",
			args: args{
				status: status.New(codes.FailedPrecondition, "just"),
			},
			wantCode: 412,
			wantMsg:  "just",
		},
		{
			name: "OutOfRange",
			args: args{
				status: status.New(codes.OutOfRange, "just"),
			},
			wantCode: 400,
			wantMsg:  "just",
		},
		{
			name: "Aborted",
			args: args{
				status: status.New(codes.Aborted, "just"),
			},
			wantCode: 409,
			wantMsg:  "just",
		},
		{
			name: "ResourceExhausted",
			args: args{
				status: status.New(codes.ResourceExhausted, "just"),
			},
			wantCode: 429,
			wantMsg:  "just",
		},
		{
			name: "DataLoss",
			args: args{
				status: status.New(codes.DataLoss, "just"),
			},
			wantCode: http.StatusInternalServerError,
			wantMsg:  "just",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if gotMsg, gotCode := grpcErrToHttpErr(tt.args.status); gotCode != tt.wantCode || gotMsg != tt.wantMsg {
				t.Errorf("grpcErrToHttpErr() = (%v, %v), want (%v, %v)", gotCode, gotMsg, tt.wantCode, tt.wantMsg)
			}
		})
	}
}
