package ovpm

import (
	"fmt"
	"io"
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/cad/ovpm/pki"
	"github.com/cad/ovpm/supervisor"
	"github.com/sirupsen/logrus"
)

var fs map[string]string

func setupTestCase() {
	// Initialize.
	fs = make(map[string]string)
	vpnProc.Stop()
}

func TestVPNInit(t *testing.T) {
	// Init:
	setupTestCase()
	CreateDB("sqlite3", ":memory:")
	defer db.Cease()
	// Prepare:
	// Test:

	// Check database if the database has no server.
	var server dbServerModel
	db.First(&server)

	// Isn't server empty struct?
	if !db.NewRecord(&server) {
		t.Fatalf("server is expected to be empty struct(new record) but it isn't %+v", server)
	}

	// Wrongfully initialize server.

	if err := TheServer().Init("localhost", "asdf", UDPProto, "", "", "", "", false); err == nil {
		t.Fatalf("error is expected to be not nil but it's nil instead")
	}

	// Initialize the server.
	TheServer().Init("localhost", "", UDPProto, "", "", "", "", false)

	// Check database if the database has no server.
	var server2 dbServerModel
	db.First(&server2)

	// Is server empty struct?
	if db.NewRecord(&server2) {
		t.Fatalf("server is expected to be not empty struct(new record) but it is %+v", server2)
	}
}

func TestVPNDeinit(t *testing.T) {
	// Init:
	setupTestCase()
	CreateDB("sqlite3", ":memory:")
	defer db.Cease()

	// Prepare:
	// Initialize the server.
	TheServer().Init("localhost", "", UDPProto, "", "", "", "", false)
	u, err := CreateNewUser("user", "p", false, 0, true, "description")
	if err != nil {
		t.Fatal(err)
	}
	u.Delete()

	// Test:
	var server dbServerModel
	db.First(&server)

	// Isn't server empty struct?
	if db.NewRecord(&server) {
		t.Fatalf("server is expected to be not empty struct(new record) but it is %+v", server)
	}

	// Test if Revoked table contains the removed user's entries.
	var revoked dbRevokedModel
	db.First(&revoked)

	if db.NewRecord(&revoked) {
		t.Errorf("revoked shouldn't be empty")
	}

	// Deinitialize.
	TheServer().Deinit()

	// Get server from db.
	var server2 dbServerModel
	db.First(&server2)

	// Isn't server empty struct?
	if !db.NewRecord(&server2) {
		t.Fatalf("server is expected to be empty struct(new record) but it is not %+v", server2)
	}

	// Test if Revoked table contains the removed user's entries.
	var revoked2 dbRevokedModel
	db.First(&revoked2)

	// Is revoked empty?
	if !db.NewRecord(&revoked2) {
		t.Errorf("revoked should be empty")
	}
}
func TestVPNUpdate(t *testing.T) {
	// Init:
	setupTestCase()
	CreateDB("sqlite3", ":memory:")
	defer db.Cease()
	// Prepare:
	TheServer().Init("localhost", "", UDPProto, "", "", "", "", false)
	// Test:

	var updatetests = []struct {
		vpnnet     string
		dns        string
		useLZO     *bool
		vpnChanged bool
		dnsChanged bool
	}{
		{"", "", nil, false, false},
		{"192.168.9.0/24", "", nil, true, false},
		{"", "2.2.2.2", nil, false, true},
		{"9.9.9.0/24", "1.1.1.1", nil, true, true},
		{"9.9.9.0/24", "1.1.1.1", nil, true, true},
	}
	for i, tt := range updatetests {
		svr := TheServer()
		svr.Init("localhost", "", UDPProto, "", "", "", "", false)

		oldIP := svr.Net
		oldDNS := svr.DNS
		svr.Update(tt.vpnnet, tt.dns, tt.useLZO)
		svr = nil
		svr = TheServer()
		if (svr.Net != oldIP) != tt.vpnChanged {
			fmt.Println(i, svr.Net, oldIP, svr.Net == oldIP, tt.vpnChanged)
			t.Fatalf("expected vpn change: %t but opposite happened", tt.vpnChanged)
		}
		if (svr.DNS != oldDNS) != tt.dnsChanged {
			t.Fatalf("expected vpn change: %t but opposite happened", tt.dnsChanged)
		}
	}

}

func TestVPNIsInitialized(t *testing.T) {
	// Init:
	setupTestCase()
	CreateDB("sqlite3", ":memory:")
	defer db.Cease()

	// Prepare:

	// Test:
	// Is initialized?
	if TheServer().IsInitialized() {
		t.Fatalf("IsInitialized() is expected to return false but it returned true")
	}

	// Initialize the server.
	TheServer().Init("localhost", "", UDPProto, "", "", "", "", false)

	// Isn't initialized?
	if !TheServer().IsInitialized() {
		t.Fatalf("IsInitialized() is expected to return true but it returned false")
	}
}

func TestVPNTheServer(t *testing.T) {
	// Init:
	setupTestCase()
	CreateDB("sqlite3", ":memory:")
	defer db.Cease()

	// Prepare:

	// Test:
	svr := TheServer()

	// Isn't server nil?
	if svr.IsInitialized() {
		t.Fatal("server is expected to be not initialized it is")
	}

	// Initialize server.
	svr.Init("localhost", "", UDPProto, "", "", "", "", false)

	svr = TheServer()

	// Is server nil?
	if !svr.IsInitialized() {
		t.Fatal("server is expected to be initialized but it's not")
	}
}

func TestVPNDumpsClientConfig(t *testing.T) {
	// Init:
	setupTestCase()
	CreateDB("sqlite3", ":memory:")
	defer db.Cease()
	svr := TheServer()
	svr.Init("localhost", "", UDPProto, "", "", "", "", false)

	// Prepare:
	user, err := CreateNewUser("user", "password", false, 0, true, "description")
	if err != nil {
		t.Fatal(err)
	}

	// Test:
	clientConfigBlob, err := svr.DumpsClientConfig(user.GetUsername())
	if err != nil {
		t.Fatalf("expected to dump client config but we got error instead: %v", err)
	}

	// Is empty?
	if len(clientConfigBlob) == 0 {
		t.Fatal("expected the dump not empty but it's empty instead")
	}
}

func TestVPNDumpClientConfig(t *testing.T) {
	// Init:
	setupTestCase()
	CreateDB("sqlite3", ":memory:")
	defer db.Cease()
	svr := TheServer()
	svr.Init("localhost", "", UDPProto, "", "", "", "", false)

	// Prepare:
	noGW := false
	user, err := CreateNewUser("user", "password", noGW, 0, true, "description")
	if err != nil {
		t.Fatalf("can not create user: %v", err)
	}

	// Test:
	if err = svr.DumpClientConfig(user.GetUsername(), "/tmp/user.ovpn"); err != nil {
		t.Fatalf("expected to dump client config but we got error instead: %v", err)
	}

	// Read file.
	clientConfigBlob := fs["/tmp/user.ovpn"]

	// Is empty?
	if len(clientConfigBlob) == 0 {
		t.Fatal("expected the dump not empty but it's empty instead")
	}

	// Is noGW honored?
	if strings.Contains(clientConfigBlob, "route-nopull") != noGW {
		logrus.Info(clientConfigBlob)
		t.Fatalf("client config generator doesn't honor NoGW")
	}

	user.Delete()

	noGW = true
	user, err = CreateNewUser("user", "password", noGW, 0, true, "description")
	if err != nil {
		t.Fatalf("can not create user: %v", err)
	}

	if err = TheServer().DumpClientConfig(user.GetUsername(), "/tmp/user.ovpn"); err != nil {
		t.Fatalf("expected to dump client config but we got error instead: %v", err)
	}

	// Read file.
	clientConfigBlob = fs["/tmp/user.ovpn"]

}

func TestVPNGetSystemCA(t *testing.T) {
	// Init:
	setupTestCase()
	CreateDB("sqlite3", ":memory:")
	defer db.Cease()

	// Prepare:
	svr := TheServer()

	// Test:
	ca, err := svr.GetSystemCA()
	if err == nil {
		t.Fatalf("GetSystemCA() is expected to give error but it didn't instead")
	}

	// Initialize system.
	svr.Init("localhost", "", UDPProto, "", "", "", "", false)

	ca, err = svr.GetSystemCA()
	if err != nil {
		t.Fatalf("GetSystemCA() is expected to get system ca, but it gave us an error instead: %v", err)
	}

	// Is it empty?
	if len(ca.Cert) == 0 {
		t.Fatalf("ca.Cert is expected to be not empty, but it's empty instead")
	}
	if len(ca.Key) == 0 {
		t.Fatalf("ca.Key is expected to be not empty, but it's empty instead")

	}
}

func TestVPNStartVPNProc(t *testing.T) {
	// Init:
	setupTestCase()
	CreateDB("sqlite3", ":memory:")
	defer db.Cease()
	svr := TheServer()

	// Prepare:

	// Test:
	// Isn't it stopped?
	if vpnProc.Status() != supervisor.STOPPED {
		t.Fatalf("expected state is STOPPED, got %s instead", vpnProc.Status())
	}

	// Call start without server initialization.
	svr.StartVPNProc()

	// Isn't it still stopped?
	if vpnProc.Status() != supervisor.STOPPED {
		t.Fatalf("expected state is STOPPED, got %s instead", vpnProc.Status())
	}

	// Initialize OVPM server.
	svr.Init("localhost", "", UDPProto, "", "", "", "", false)

	// Call start again..
	svr.StartVPNProc()

	// Isn't it RUNNING?
	if vpnProc.Status() != supervisor.RUNNING {
		t.Fatalf("expected state is RUNNING, got %s instead", vpnProc.Status())
	}
}

func TestVPNStopVPNProc(t *testing.T) {
	// Init:
	setupTestCase()
	CreateDB("sqlite3", ":memory:")
	defer db.Cease()
	svr := TheServer()
	svr.Init("localhost", "", UDPProto, "", "", "", "", false)

	// Prepare:
	vpnProc.Start()

	// Test:
	// Isn't it running?
	if vpnProc.Status() != supervisor.RUNNING {
		t.Fatalf("expected state is RUNNING, got %s instead", vpnProc.Status())
	}

	// Call stop.
	svr.StopVPNProc()

	// Isn't it stopped?
	if vpnProc.Status() != supervisor.STOPPED {
		t.Fatalf("expected state is STOPPED, got %s instead", vpnProc.Status())
	}
}

func TestVPNRestartVPNProc(t *testing.T) {
	// Init:
	CreateDB("sqlite3", ":memory:")
	defer db.Cease()
	svr := TheServer()
	svr.Init("localhost", "", UDPProto, "", "", "", "", false)

	// Prepare:

	// Test:

	// Call restart.
	svr.RestartVPNProc()

	// Isn't it running?
	if vpnProc.Status() != supervisor.RUNNING {
		t.Fatalf("expected state is RUNNING, got %s instead", vpnProc.Status())
	}

	// Call restart again.
	svr.RestartVPNProc()

	// Isn't it running?
	if vpnProc.Status() != supervisor.RUNNING {
		t.Fatalf("expected state is RUNNING, got %s instead", vpnProc.Status())
	}
}

func TestVPNEmit(t *testing.T) {
	// Init:
	setupTestCase()
	CreateDB("sqlite3", ":memory:")
	defer db.Cease()
	svr := TheServer()
	svr.Init("localhost", "", UDPProto, "", "", "", "", false)

	// Prepare:

	// Test:
	svr.Emit()

	var emittests = []string{
		_DefaultVPNConfPath,
		_DefaultKeyPath,
		_DefaultCertPath,
		_DefaultCRLPath,
		_DefaultCACertPath,
		_DefaultCAKeyPath,
		_DefaultDHParamsPath,
	}

	for _, tt := range emittests {
		if len(fs[tt]) == 0 {
			t.Errorf("%s is expected to be not empty but it is", tt)
		}
	}

	// TODO(cad): Write test cases for ccd/ files as well.
}

func TestVPNemitToFile(t *testing.T) {
	// Initialize:

	// Prepare:
	path := "/test/file"
	content := "blah blah blah"

	// Test:
	// Is path exist?
	if _, ok := fs[path]; ok {
		t.Fatalf("key '%s' expected to be non-existent on fs, but it is instead", path)
	}

	// Emit the contents.

	if err := TheServer().emitToFile(path, content, 0); err != nil {
		t.Fatalf("expected  to be able to emit to the filesystem but we got this error instead: %v", err)
	}

	// Is the content on the filesystem correct?
	if fs[path] != content {
		t.Fatalf("content on the filesytem is expected to be same with '%s' but it's '%s' instead", content, fs[path])
	}
}

type fakeProcess struct {
	state supervisor.State
}

func (f *fakeProcess) Start() {
	f.state = supervisor.RUNNING
}

func (f *fakeProcess) Stop() {
	f.state = supervisor.STOPPED
}

func (f *fakeProcess) Restart() {
	f.state = supervisor.RUNNING
}

func (f *fakeProcess) Status() supervisor.State {
	return f.state
}

func TestGetConnectedUsers(t *testing.T) {
	// Init:
	setupTestCase()
	CreateDB("sqlite3", ":memory:")
	defer db.Cease()
	svr := TheServer()
	svr.Init("localhost", "", UDPProto, "", "", "", "", false)

	// Mock funcs.
	svr.openFunc = func(path string) (io.Reader, error) {
		return nil, nil
	}
	// Create the corresponding users for test.
	usr1, err := CreateNewUser("usr1", "1234", true, 0, false, "description")
	if err != nil {
		t.Fatalf("user creation failed: %v", err)
	}
	usr2, err := CreateNewUser("usr2", "1234", true, 0, false, "description")
	if err != nil {
		t.Fatalf("user creation failed: %v", err)
	}
	now := time.Now()
	svr.parseStatusLogFunc = func(f io.Reader) ([]clEntry, []rtEntry) {
		clt := []clEntry{
			clEntry{
				CommonName:     usr1.GetUsername(),
				RealAddress:    "1.1.1.1",
				ConnectedSince: now,
				BytesReceived:  1,
				BytesSent:      5,
			},
			clEntry{
				CommonName:     usr2.GetUsername(),
				RealAddress:    "1.1.1.2",
				ConnectedSince: now,
				BytesReceived:  2,
				BytesSent:      6,
			},
		}
		rtt := []rtEntry{
			rtEntry{
				CommonName:     usr1.GetUsername(),
				RealAddress:    "1.1.1.1",
				LastRef:        now,
				VirtualAddress: "10.10.10.1",
			},
			rtEntry{
				CommonName:     usr2.GetUsername(),
				RealAddress:    "1.1.1.2",
				LastRef:        now,
				VirtualAddress: "10.10.10.2",
			},
		}
		return clt, rtt
	}

	// Test:
	tests := []struct {
		name    string
		want    []User
		wantErr bool
	}{
		{"default", []User{*usr2, *usr1}, false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := TheServer().GetConnectedUsers()
			if (err != nil) != tt.wantErr {
				t.Errorf("GetConnectedUsers() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			for _, wu := range tt.want {
				var found bool
				for _, u := range got {
					if wu.GetUsername() == u.GetUsername() {
						found = true
					}
				}
				if !found {
					t.Errorf("wanted user (%s) is not present in the response we got %v", wu.GetUsername(), got)
				}
			}
		})
	}
}

func TestVPN_ExpiresAt(t *testing.T) {
	// Initialize:
	db := CreateDB("sqlite3", ":memory:")
	defer db.Cease()
	svr := TheServer()
	svr.Init("localhost", "", UDPProto, "", "", "", "", false)

	// Test:
	cert, err := pki.ReadCertFromPEM(svr.Cert)
	if err != nil {
		t.Fatalf("test preperation failed: %v", err)
	}

	if !reflect.DeepEqual(svr.ExpiresAt(), cert.NotAfter) {
		t.Errorf("got (%s), want (%s)", svr.ExpiresAt(), cert.NotAfter)
	}
}

func TestVPN_CAExpiresAt(t *testing.T) {
	// Initialize:
	db := CreateDB("sqlite3", ":memory:")
	defer db.Cease()
	svr := TheServer()
	svr.Init("localhost", "", UDPProto, "", "", "", "", false)

	// Test:
	cert, err := pki.ReadCertFromPEM(svr.CACert)
	if err != nil {
		t.Fatalf("test preperation failed: %v", err)
	}
	if !reflect.DeepEqual(svr.CAExpiresAt(), cert.NotAfter) {
		t.Errorf("got (%s), want (%s)", svr.CAExpiresAt(), cert.NotAfter)
	}
}

func init() {
	// Init
	Testing = true
	fs = make(map[string]string)

	CreateDB("sqlite3", ":memory:")
	defer db.Cease()

	// Monkeypatch emitToFile()
	TheServer().emitToFileFunc = func(path, content string, mode uint) error {
		fs[path] = content
		return nil
	}
	vpnProc = &fakeProcess{state: supervisor.STOPPED}
}
