package websocket

import (
	"context"
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	m "github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/httpclient"
	"github.com/fiskerinc/cloud-services/pkg/httpclient/mock"
	kafka "github.com/fiskerinc/cloud-services/pkg/kafka/mock"
	"github.com/fiskerinc/cloud-services/pkg/testhelper"

	"github.com/gobwas/httphead"
	"github.com/gobwas/ws"
	"github.com/gobwas/ws/wsutil"
)

func createMockWebsocketClient(url, userAgent string) net.Conn {
	u := "ws" + strings.TrimPrefix(url, "http")

	header := make(http.Header)
	header.Add("User-Agent", userAgent)
	header.Add("X-ICCID", "12345678912345678923456789")
	header.Add("Ssl-Client-Subject-Dn", "CN=1F15K3R45N1234567")
	dialer := ws.Dialer{
		Header:     ws.HandshakeHeaderHTTP(header),
		Extensions: []httphead.Option{httphead.NewOption("permessage-deflate", map[string]string{})},
	}

	ctx := context.Background()
	ws, _, _, err := dialer.Dial(ctx, u)
	if err != nil {
		return nil
	}
	return ws
}

func TestSecureSessionTRex(t *testing.T) {
	userAgent := "Fisker Ocean T.Rex 1.2.3.4 abc123"

	createNewSession := func(w http.ResponseWriter, r *http.Request) {
		s, err := NewSecureSession(w, r)
		if err != nil {
			t.Errorf(testhelper.TestErrorTemplate, "TestNewSessionTRex", nil, err)
		}
		defer s.Close()

		if fmt.Sprintf("%T", s) != "*websocket.SessionTRex" {
			t.Errorf(testhelper.TestErrorTemplate, "TestNewSessionTRex", "*websocket.SessionTRex", fmt.Sprintf("%T", s))
		}
	}

	server := httptest.NewServer(http.HandlerFunc(createNewSession))
	defer server.Close()

	conn := createMockWebsocketClient(server.URL, userAgent)
	defer conn.Close()
}

func TestSecureSessionHMI(t *testing.T) {
	userAgent := "HMI 2.0.0.0"

	createNewSession := func(w http.ResponseWriter, r *http.Request) {
		s, err := NewSecureSession(w, r)
		if err != nil {
			t.Errorf(testhelper.TestErrorTemplate, "TestNewSessionHMI", nil, err)
		}
		defer s.Close()

		if fmt.Sprintf("%T", s) != "*websocket.SessionHMI" {
			t.Errorf(testhelper.TestErrorTemplate, "TestNewSessionHMI", "*websocket.SessionHMI", fmt.Sprintf("%T", s))
		}
	}

	server := httptest.NewServer(http.HandlerFunc(createNewSession))
	defer server.Close()

	conn := createMockWebsocketClient(server.URL, userAgent)
	defer conn.Close()
}

func TestInsecureSessionMobile(t *testing.T) {
	userAgent := "Mobile 1.2.3.4"

	createNewSession := func(w http.ResponseWriter, r *http.Request) {
		s, err := NewInsecureSession(w, r)
		if err != nil {
			t.Errorf(testhelper.TestErrorTemplate, "TestNewSessionMobile", nil, err)
		}
		defer s.Close()

		if fmt.Sprintf("%T", s) != "*websocket.SessionMobile" {
			t.Errorf(testhelper.TestErrorTemplate, "TestNewSessionMobile", "*websocket.SessionMobile", fmt.Sprintf("%T", s))
		}
	}

	server := httptest.NewServer(http.HandlerFunc(createNewSession))
	defer server.Close()

	conn := createMockWebsocketClient(server.URL, userAgent)
	defer conn.Close()
}

func TestSession(t *testing.T) {
	ws, _ := net.Pipe()
	s := &Session{
		Websocket: ws,
	}

	if fmt.Sprintf("%T", s) != "*websocket.Session" {
		t.Errorf(testhelper.TestErrorTemplate, "TestSession", "*websocket.Session", fmt.Sprintf("%T", s))
	}
}

func TestNewSession(t *testing.T) {
	userAgent := ""

	createNewSession := func(w http.ResponseWriter, r *http.Request) {
		s, err := NewSession(w, r)
		if err != nil {
			t.Errorf(testhelper.TestErrorTemplate, "TestNewSession", nil, err)
		}
		defer s.Close()

		if fmt.Sprintf("%T", s) != "*websocket.Session" {
			t.Errorf(testhelper.TestErrorTemplate, "TestNewSession", "*websocket.Session", fmt.Sprintf("%T", s))
		}
	}

	server := httptest.NewServer(http.HandlerFunc(createNewSession))
	defer server.Close()

	conn := createMockWebsocketClient(server.URL, userAgent)
	defer conn.Close()
}

func TestSessionAuthenticate(t *testing.T) {
	c := mock.Client{
		DoFunc: func(*http.Request) (*http.Response, error) {
			return &http.Response{StatusCode: 200}, nil
		},
	}
	httpclient.Client = &c
	userAgent := ""

	createNewSession := func(w http.ResponseWriter, r *http.Request) {
		s, err := NewSession(w, r)
		if err != nil {
			t.Errorf(testhelper.TestErrorTemplate, "TestSessionAuthenticate", nil, err)
		}
		defer s.Close()

		err = s.Authenticate()
		if err != nil {
			t.Errorf(testhelper.TestErrorTemplate, "TestSessionAuthenticate", nil, err)
		}
	}

	server := httptest.NewServer(http.HandlerFunc(createNewSession))
	defer server.Close()

	conn := createMockWebsocketClient(server.URL, userAgent)
	defer conn.Close()

	ae := AuthEvent{
		Topic: "auth_service",
		Key:   "FISKER123",
		Payload: AuthPayload{
			Handler: "verify",
			Data: AuthData{
				Token: "validtoken",
			},
		},
	}

	msg, err := json.Marshal(ae)
	if err != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestSessionAuthenticate", nil, err)
	}

	wsutil.WriteClientMessage(conn, ws.OpText, msg)
}

func TestSessionKey(t *testing.T) {
	ws, _ := net.Pipe()
	s := &Session{
		Websocket: ws,
		Type:      m.TRex,
		ID:        "FISKER123",
	}

	key := s.Key()
	if key != "1:FISKER123" {
		t.Errorf(testhelper.TestErrorTemplate, "TestSessionKey", "1:FISKER123", key)
	}
}

func TestSessionKeyUnknown(t *testing.T) {
	ws, _ := net.Pipe()
	s := &Session{
		Websocket: ws,
		Type:      m.Unknown,
		ID:        "FISKER123",
	}

	key := s.Key()
	if key != "FISKER123" {
		t.Errorf(testhelper.TestErrorTemplate, "TestSessionKeyUnknown", "FISKER123", key)
	}
}

func TestSessionReceive(t *testing.T) {
	userAgent := ""
	payload := "hello fisker!"

	createNewSession := func(w http.ResponseWriter, r *http.Request) {
		s, err := NewSession(w, r)
		if err != nil {
			t.Errorf(testhelper.TestErrorTemplate, "TestSessionReceive", nil, err)
		}
		defer s.Close()

		data, _, err := s.Receive()
		if err != nil {
			t.Errorf(testhelper.TestErrorTemplate, "TestSessionReceive", nil, err)
		}

		msg := string(data)
		if msg != payload {
			t.Errorf(testhelper.TestErrorTemplate, "TestSessionReceive", payload, msg)
		}
	}

	server := httptest.NewServer(http.HandlerFunc(createNewSession))
	defer server.Close()

	conn := createMockWebsocketClient(server.URL, userAgent)
	defer conn.Close()

	err := wsutil.WriteClientMessage(conn, ws.OpText, []byte(payload))
	if err != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestSessionReceive", nil, err)
	}
}

func TestSessionListen(t *testing.T) {
	userAgent := ""
	payload := "hello fisker!"

	createNewSession := func(w http.ResponseWriter, r *http.Request) {
		s, err := NewSession(w, r)
		if err != nil {
			t.Errorf(testhelper.TestErrorTemplate, "TestSessionListen", nil, err)
		}
		defer s.Close()

		ctx := context.Background()
		err = s.Listen(ctx, kafka.GetKafkaMock(nil))
		if err.Error() != "EOF" {
			t.Errorf(testhelper.TestErrorTemplate, "TestSessionListen", nil, err)
		}
	}

	server := httptest.NewServer(http.HandlerFunc(createNewSession))
	defer server.Close()

	conn := createMockWebsocketClient(server.URL, userAgent)
	defer conn.Close()

	err := wsutil.WriteClientMessage(conn, ws.OpText, []byte(payload))
	if err != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestSessionListen", nil, err)
	}
}

func TestSessionSend(t *testing.T) {
	userAgent := ""
	payload := "hello fisker!"

	createNewSession := func(w http.ResponseWriter, r *http.Request) {
		s, err := NewSession(w, r)
		if err != nil {
			t.Errorf(testhelper.TestErrorTemplate, "TestSessionSend", nil, err)
		}
		defer s.Close()

		data, _, err := s.Receive()
		if err != nil {
			t.Errorf(testhelper.TestErrorTemplate, "TestSessionSend", nil, err)
		}

		msg := string(data)
		if msg != payload {
			t.Errorf(testhelper.TestErrorTemplate, "TestSessionSend", payload, msg)
		}

		err = s.SendMsgToClient(data)
		if err != nil {
			t.Errorf(testhelper.TestErrorTemplate, "TestSessionSend", nil, err)
		}
	}

	server := httptest.NewServer(http.HandlerFunc(createNewSession))
	defer server.Close()

	conn := createMockWebsocketClient(server.URL, userAgent)
	defer conn.Close()

	err := wsutil.WriteClientMessage(conn, ws.OpText, []byte(payload))
	if err != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestSessionSend", nil, err)
	}

	echo, _, err := wsutil.ReadServerData(conn)
	if err != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestSessionSend", nil, err)
	}

	message := string(echo)
	if message != payload {
		t.Errorf(testhelper.TestErrorTemplate, "TestSessionSend", payload, message)
	}
}

func TestSessionLoad(t *testing.T) {
	ws, _ := net.Pipe()
	s := &Session{
		Websocket: ws,
		Type:      m.TRex,
		ID:        "FISKER123",
	}

	err := s.Load(kafka.GetKafkaMock(nil))
	if err != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestSessionLoad", nil, err)
	}
}

func TestSessionTeardown(t *testing.T) {
	ws, _ := net.Pipe()
	s := &Session{
		Websocket: ws,
		Type:      m.TRex,
		ID:        "FISKER123",
	}

	err := s.Load(kafka.GetKafkaMock(nil))
	if err != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestSessionLoad", nil, err)
	}

	err = s.Teardown(kafka.GetKafkaMock(nil))
	if err != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestSessionLoad", nil, err)
	}
}

func TestSessionGetUUID(t *testing.T) {
	ws, _ := net.Pipe()
	currentNanoSeconds := time.Now().UnixNano()
	s := &Session{
		Websocket: ws,
		Type:      m.TRex,
		ID:        "FISKER123",
		epoch:     currentNanoSeconds,
	}

	uuid := s.GetUUID()
	if uuid != currentNanoSeconds {
		t.Errorf(testhelper.TestErrorTemplate, "TestSessionLoad", currentNanoSeconds, uuid)
	}
}

type SessionGetVINTestCase struct {
	Name             string
	Session          *Session
	VIN              string
	ExpectedErrorMsg string
}

func TestSessionGetVIN(t *testing.T) {
	ws, _ := net.Pipe()

	tests := []SessionGetVINTestCase{
		{
			Name: "Parsable",
			Session: &Session{
				Websocket: ws,
				Type:      m.TRex,
				ID:        "VCF1EBU22PG001732",
			},
			VIN: "VCF1EBU22PG001732",
		},
		{
			Name: "Not Parsable",
			Session: &Session{
				Websocket: ws,
				Type:      m.TRex,
				ID:        "FISKER123",
			},
			VIN:              "FISKER123",
			ExpectedErrorMsg: "could not get VIN from session",
		},
	}

	for _, tt := range tests {
		vin := tt.Session.GetID()

		if vin != tt.VIN {
			t.Errorf(testhelper.TestErrorTemplate, "TestSessionGetVIN", tt.VIN, vin)
		}
	}
}
