package service

import (
	"encoding/json"
	"regexp"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/paralus/paralus/pkg/audit"
	eventv1 "github.com/paralus/paralus/proto/rpc/audit"
	auditv1 "github.com/paralus/paralus/proto/types/audit"
	commonv3 "github.com/paralus/paralus/proto/types/commonpb/v3"
)

const project = "projectone"

func TestAuditLog(t *testing.T) {

	testcases := []struct {
		name string
		run  func(t *testing.T)
	}{
		{
			name: "auditlog-lasthour",
			run:  testGetAuditLogForLastHour(audit.SYSTEM),
		},
		{
			name: "auditlog-lastday",
			run:  testGetAuditLogForDay(audit.SYSTEM),
		},
		{
			name: "kubectlcmd-lastday",
			run:  testGetKubectlCommands(audit.KUBECTL_CMD),
		},
	}

	t.Run("auditlogs", func(t *testing.T) {
		for _, testcase := range testcases {
			tc := testcase
			t.Run(tc.name, func(t *testing.T) {
				t.Parallel()
				tc.run(t)
			})
		}
	})
}

func testGetAuditLogForLastHour(tag string) func(t *testing.T) {
	return func(t *testing.T) {

		db, mock := getDB(t)
		defer db.Close()

		as, err := NewAuditLogDatabaseService(db, tag)
		if err != nil {
			t.Fatal(err)
		}

		timefrom := "1h"
		auditrecord := "{\"actor\":{\"account\":{\"username\":\"admin@paralus.local\"},\"groups\":[\"All Local Users\",\"Organization Admins\"],\"type\":\"USER\"},\"category\":\"AUDIT\",\"client\":{\"host\":\"console-ic-oss.dev.rafay-edge.net\",\"ip\":\"10.0.0.147:47204\",\"type\":\"BROWSER\",\"user_agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36\"},\"detail\":{\"message\":\"Cluster kind-local deleted\",\"meta\":{\"cluster_name\":\"kind-local\"}},\"origin\":\"core\",\"portal\":\"OPS\",\"project\":\"stage\",\"timestamp\":\"2022-11-21T09:48:30.597615647Z\",\"type\":\"cluster.delete.success\",\"version\":\"1.0\"}"

		req := &eventv1.GetAuditLogSearchRequest{
			Metadata: &commonv3.Metadata{
				UrlScope: "auditlogs/" + project,
			},
			Filter: &eventv1.AuditLogQueryFilter{
				Timefrom: "now-" + timefrom,
				Projects: []string{project},
			},
		}

		mock.ExpectQuery(regexp.QuoteMeta(`SELECT "auditlog"."tag", "auditlog"."time", "auditlog"."data" FROM "audit_logs" AS "auditlog" WHERE (tag = '` + tag + `') AND (data->>'project' = '` + project + `') AND (time between now() - interval '` + timefrom + `' and now())`)).
			WillReturnRows(sqlmock.NewRows([]string{"tag", "time", "data"}).AddRow("system", time.Now(), auditrecord))

		mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(1) as count, data->>'project' as key FROM "audit_logs" WHERE (tag = '` + tag + `') AND (data->>'project' = '` + project + `') AND (time between now() - interval '` + timefrom + `' and now()) GROUP BY data->>'project'`)).
			WillReturnRows(sqlmock.NewRows([]string{"count", "key"}).AddRow(1, "cluster"))

		mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(1) as count, data->'actor'->'account'->>'username' as key FROM "audit_logs" WHERE (tag = '` + tag + `') AND (data->>'project' = '` + project + `') AND (time between now() - interval '` + timefrom + `' and now()) GROUP BY data->'actor'->'account'->>'username'`)).
			WillReturnRows(sqlmock.NewRows([]string{"count", "key"}).AddRow(1, "username"))

		mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(1) as count, data->>'type' as key FROM "audit_logs" WHERE (tag = '` + tag + `') AND (data->>'project' = '` + project + `') AND (time between now() - interval '` + timefrom + `' and now()) GROUP BY data->>'type'`)).
			WillReturnRows(sqlmock.NewRows([]string{"count", "key"}).AddRow(1, "type"))

		res, err := as.GetAuditLogByProjects(req)
		if err != nil {
			t.Fatal("could not get audit logs:", err)
		}

		var response auditv1.AuditResponse
		data, err := json.Marshal(res.Result)
		if err != nil {
			t.Fatal(err)
		}
		err = json.Unmarshal(data, &response)
		if err != nil {
			t.Fatal(err)
		}

		if len(response.GetHits().Hits) != 1 {
			t.Fail()
		}
	}
}

func testGetAuditLogForDay(tag string) func(t *testing.T) {
	return func(t *testing.T) {

		db, mock := getDB(t)
		defer db.Close()

		as, err := NewAuditLogDatabaseService(db, tag)
		if err != nil {
			t.Fatal(err)
		}

		timefrom := "1d"
		auditrecord := "{\"actor\":{\"account\":{\"username\":\"admin@paralus.local\"},\"groups\":[\"All Local Users\",\"Organization Admins\"],\"type\":\"USER\"},\"category\":\"AUDIT\",\"client\":{\"host\":\"console-ic-oss.dev.rafay-edge.net\",\"ip\":\"10.0.0.147:47204\",\"type\":\"BROWSER\",\"user_agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36\"},\"detail\":{\"message\":\"Cluster kind-local deleted\",\"meta\":{\"cluster_name\":\"kind-local\"}},\"origin\":\"core\",\"portal\":\"OPS\",\"project\":\"stage\",\"timestamp\":\"2022-11-21T09:48:30.597615647Z\",\"type\":\"cluster.delete.success\",\"version\":\"1.0\"}"
		auditrecordtwo := "{\"actor\":{\"account\":{\"username\":\"admin@paralus.local\"},\"groups\":[\"All Local Users\",\"Organization Admins\"],\"type\":\"USER\"},\"category\":\"AUDIT\",\"client\":{\"host\":\"console-ic-oss.dev.rafay-edge.net\",\"ip\":\"10.0.0.147:47204\",\"type\":\"BROWSER\",\"user_agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36\"},\"detail\":{\"message\":\"Cluster kind-local deleted\",\"meta\":{\"cluster_name\":\"kind-local\"}},\"origin\":\"core\",\"portal\":\"OPS\",\"project\":\"stage\",\"timestamp\":\"2022-11-21T09:48:30.597615647Z\",\"type\":\"cluster.delete.success\",\"version\":\"1.0\"}"

		req := &eventv1.GetAuditLogSearchRequest{
			Metadata: &commonv3.Metadata{
				UrlScope: "auditlogs/" + project,
			},
			Filter: &eventv1.AuditLogQueryFilter{
				Timefrom: "now-" + timefrom,
			},
		}

		mock.ExpectQuery(regexp.QuoteMeta(`SELECT "auditlog"."tag", "auditlog"."time", "auditlog"."data" FROM "audit_logs" AS "auditlog" WHERE (tag = '` + tag + `') AND (data->>'project' = '` + project + `') AND (time between now() - interval '` + timefrom + `' and now())`)).
			WillReturnRows(sqlmock.NewRows([]string{"tag", "time", "data"}).AddRow(tag, time.Now(), auditrecord).AddRow(tag, time.Now(), auditrecordtwo))

		mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(1) as count, data->>'project' as key FROM "audit_logs" WHERE (tag = '` + tag + `') AND (data->>'project' = '` + project + `') AND (time between now() - interval '` + timefrom + `' and now()) GROUP BY data->>'project'`)).
			WillReturnRows(sqlmock.NewRows([]string{"count", "key"}).AddRow(1, "project"))

		mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(1) as count, data->'actor'->'account'->>'username' as key FROM "audit_logs" WHERE (tag = '` + tag + `') AND (data->>'project' = '` + project + `') AND (time between now() - interval '` + timefrom + `' and now()) GROUP BY data->'actor'->'account'->>'username'`)).
			WillReturnRows(sqlmock.NewRows([]string{"count", "key"}).AddRow(1, "username"))

		mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(1) as count, data->>'type' as key FROM "audit_logs" WHERE (tag = '` + tag + `') AND (data->>'project' = '` + project + `') AND (time between now() - interval '` + timefrom + `' and now()) GROUP BY data->>'type'`)).
			WillReturnRows(sqlmock.NewRows([]string{"count", "key"}).AddRow(1, "type"))

		res, err := as.GetAuditLog(req)
		if err != nil {
			t.Fatal("could not get audit logs:", err)
		}

		var response auditv1.AuditResponse
		data, err := json.Marshal(res.Result)
		if err != nil {
			t.Fatal(err)
		}
		err = json.Unmarshal(data, &response)
		if err != nil {
			t.Fatal(err)
		}

		if len(response.GetHits().Hits) != 2 {
			t.Fail()
		}
	}
}

func testGetKubectlCommands(tag string) func(t *testing.T) {
	return func(t *testing.T) {

		db, mock := getDB(t)
		defer db.Close()

		as, err := NewAuditLogDatabaseService(db, tag)
		if err != nil {
			t.Fatal(err)
		}

		timefrom := "1d"
		auditrecord := "{\"actor\":{\"account\":{\"username\":\"admin@paralus.local\"},\"groups\":[\"All Local Users\",\"Organization Admins\"],\"type\":\"USER\"},\"category\":\"AUDIT\",\"client\":{\"host\":\"console-ic-oss.dev.rafay-edge.net\",\"ip\":\"122.50.194.215\",\"type\":\"BROWSER\",\"user_agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36\"},\"detail\":{\"message\":\"kubectl get all\",\"meta\":{\"cluster_name\":\"kind-local\"}},\"origin\":\"cluster\",\"portal\":\"ADMIN\",\"project\":\"stage\",\"timestamp\":\"2022-11-21T09:46:30.854455438Z\",\"type\":\"kubectl.command.detail\",\"version\":\"1.0\"}"
		auditrecordtwo := "{\"actor\":{\"account\":{\"username\":\"admin@paralus.local\"},\"groups\":[\"All Local Users\",\"Organization Admins\"],\"type\":\"USER\"},\"category\":\"AUDIT\",\"client\":{\"host\":\"console-ic-oss.dev.rafay-edge.net\",\"ip\":\"122.50.194.215\",\"type\":\"BROWSER\",\"user_agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36\"},\"detail\":{\"message\":\"kubectl get all\",\"meta\":{\"cluster_name\":\"kind-local\"}},\"origin\":\"cluster\",\"portal\":\"ADMIN\",\"project\":\"stage\",\"timestamp\":\"2022-11-21T09:46:30.854455438Z\",\"type\":\"kubectl.command.detail\",\"version\":\"1.0\"}"

		req := &eventv1.GetAuditLogSearchRequest{
			Metadata: &commonv3.Metadata{
				UrlScope: "auditlogs/" + project,
			},
			Filter: &eventv1.AuditLogQueryFilter{
				Timefrom: "now-" + timefrom,
			},
		}

		mock.ExpectQuery(regexp.QuoteMeta(`SELECT "auditlog"."tag", "auditlog"."time", "auditlog"."data" FROM "audit_logs" AS "auditlog" WHERE (tag = '` + tag + `') AND (data->>'project' = '` + project + `') AND (time between now() - interval '` + timefrom + `' and now())`)).
			WillReturnRows(sqlmock.NewRows([]string{"tag", "time", "data"}).AddRow(tag, time.Now(), auditrecord).AddRow(tag, time.Now(), auditrecordtwo))

		mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(1) as count, data->>'project' as key FROM "audit_logs" WHERE (tag = '` + tag + `') AND (data->>'project' = '` + project + `') AND (time between now() - interval '` + timefrom + `' and now()) GROUP BY data->>'project'`)).
			WillReturnRows(sqlmock.NewRows([]string{"count", "key"}).AddRow(1, "project"))

		mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(1) as count, data->'actor'->'account'->>'username' as key FROM "audit_logs" WHERE (tag = '` + tag + `') AND (data->>'project' = '` + project + `') AND (time between now() - interval '` + timefrom + `' and now()) GROUP BY data->'actor'->'account'->>'username'`)).
			WillReturnRows(sqlmock.NewRows([]string{"count", "key"}).AddRow(1, "username"))

		mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(1) as count, data->>'type' as key FROM "audit_logs" WHERE (tag = '` + tag + `') AND (data->>'project' = '` + project + `') AND (time between now() - interval '` + timefrom + `' and now()) GROUP BY data->>'type'`)).
			WillReturnRows(sqlmock.NewRows([]string{"count", "key"}).AddRow(1, "type"))

		res, err := as.GetAuditLog(req)
		if err != nil {
			t.Fatal("could not get audit logs:", err)
		}

		var response auditv1.AuditResponse
		data, err := json.Marshal(res.Result)
		if err != nil {
			t.Fatal(err)
		}
		err = json.Unmarshal(data, &response)
		if err != nil {
			t.Fatal(err)
		}

		if len(response.GetHits().Hits) != 2 {
			t.Fail()
		}
	}
}
