package auditlog

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"testing"
	"time"

	"github.com/onsi/gomega"
	"github.com/opensearch-project/opensearch-go"
	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic/pkg/config"
	"github.com/pingcap-inc/clinic/pkg/es"
)

func Test_AddAuditLog(t *testing.T) {
	g := gomega.NewWithT(t)

	cfg := config.LogConfig{
		Endpoint: fmt.Sprintf("http://127.0.0.1:%d", es.EsPort),
		KibanaConfig: config.KibanaConfig{
			AddressConfig: config.AddressConfig{
				Host: "127.0.0.1",
				Port: es.KibanaPort,
			},
		},
		Password: "admin",
		User:     "admin",
	}

	cli, err := NewAuditLogClient(cfg)
	g.Expect(err).To(gomega.Succeed())

	email := "clinic@pingcap.com"
	cli.Do(&AuditLog{
		Email:     email,
		Operator:  "GET",
		Timestamp: 1635240046000,
	})

	g.Eventually(func() bool {
		records, err := queryAuditLog(email, 1635240016000, 1635240066000, cli.cli)
		if err != nil {
			log.Error("query data from es failed", zap.Error(err))
			return false
		}

		if len(records) == 1 && records[0].Operator == "GET" {
			return true
		}

		return false
	}, time.Second*2, time.Millisecond*300).Should(gomega.BeTrue())
}

func queryAuditLog(email string, startTime, endTime uint64, cli *opensearch.Client) ([]AuditLog, error) {
	query := `{
	"query": {
        "bool": {
            "filter": [
                {
                    "term": {
                        "email": "%s"
                    }
                },
                {
                    "range": {
                        "timestamp": {
                            "gte": "%d",
                            "lt": "%d"
                        }
                    }
                }
            ]
        }
    }
}`
	query = fmt.Sprintf(query, email, startTime, endTime)
	var mjson map[string]any
	err := json.Unmarshal([]byte(query), &mjson)
	if err != nil {
		return nil, err
	}

	var buf bytes.Buffer
	err = json.NewEncoder(&buf).Encode(mjson)
	if err != nil {
		return nil, err
	}

	res, err := cli.Search(cli.Search.WithContext(context.Background()),
		cli.Search.WithIndex(index),
		cli.Search.WithBody(&buf),
		cli.Search.WithSize(1),
		cli.Search.WithTrackTotalHits(true),
		cli.Search.WithPretty())
	if err != nil {
		return nil, errors.Errorf("query=%s, err=%v", query, err)
	}

	if res.IsError() {
		var e map[string]any
		if err := json.NewDecoder(res.Body).Decode(&e); err != nil {
			log.Error("Error parsing the response body", zap.Error(err))
			return nil, err
		} else {
			return nil, fmt.Errorf("query failed. query=%s, status=%s, type=%s, reason=%s.", query, res.Status(), e["error"].(map[string]any)["type"], e["error"].(map[string]any)["reason"])
		}
	}

	var r map[string]any
	if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
		log.Error("Error parsing the response body", zap.Error(err))
		return nil, err
	}

	logs := make([]AuditLog, 0)
	if r["hits"].(map[string]any)["total"].(map[string]any)["value"].(float64) == 0 {
		return nil, nil
	}

	for _, hit := range r["hits"].(map[string]any)["hits"].([]any) {
		hs := hit.(map[string]any)["_source"].(map[string]any)
		logs = append(logs, AuditLog{
			Timestamp: int64(hs["timestamp"].(float64)),
			Email:     hs["email"].(string),
			UserName:  hs["username"].(string),
			Operator:  hs["operator"].(string),
			Resource:  hs["resource"].(string),
		})
	}

	return logs, nil
}
