package argo

import (
	"bytes"
	"sync"
	"testing"

	log "github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes/fake"

	argoappv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
)

const (
	_somecomponent = "somecomponent"
	_test          = "test"
)

var testEnableEventLog = []string{_somecomponent, _test}

// logCaptureMutex ensures that only one captureLogEntries call can run at a time
// to prevent log entries from parallel test runs from ending up in wrong buffers
var logCaptureMutex sync.Mutex

// ThreadSafeBuffer wraps bytes.Buffer with a mutex to make it thread-safe
type ThreadSafeBuffer struct {
	buf bytes.Buffer
	mu  sync.RWMutex
}

func (tsb *ThreadSafeBuffer) Write(p []byte) (n int, err error) {
	tsb.mu.Lock()
	defer tsb.mu.Unlock()
	return tsb.buf.Write(p)
}

func (tsb *ThreadSafeBuffer) String() string {
	tsb.mu.RLock()
	defer tsb.mu.RUnlock()
	return tsb.buf.String()
}

// Helper to capture log entries generated by the logger and return it as string
func captureLogEntries(run func()) string {
	logCaptureMutex.Lock()
	defer logCaptureMutex.Unlock()

	f := log.StandardLogger().Formatter
	log.SetFormatter(&log.TextFormatter{DisableColors: true})
	defer log.SetFormatter(f)

	output := &ThreadSafeBuffer{}
	original := log.StandardLogger().Out
	log.SetOutput(output)
	defer log.SetOutput(original)

	run()

	return output.String()
}

func TestNewAuditLogger(t *testing.T) {
	logger := NewAuditLogger(fake.NewClientset(), _somecomponent, testEnableEventLog)
	assert.NotNil(t, logger)
}

func TestLogAppProjEvent(t *testing.T) {
	logger := NewAuditLogger(fake.NewClientset(), _somecomponent, testEnableEventLog)
	assert.NotNil(t, logger)

	proj := argoappv1.AppProject{
		ObjectMeta: metav1.ObjectMeta{
			Name:            "default",
			Namespace:       "argocd",
			ResourceVersion: "1",
			UID:             "a-b-c-d-e",
		},
		Spec: argoappv1.AppProjectSpec{
			Description: "Test project",
		},
	}

	ei := EventInfo{
		Reason: _test,
		Type:   "info",
	}

	output := captureLogEntries(func() {
		logger.LogAppProjEvent(&proj, ei, "This is a test message", "")
	})

	assert.Contains(t, output, "level=info")
	assert.Contains(t, output, "project=default")
	assert.Contains(t, output, "reason=test")
	assert.Contains(t, output, "type=info")
	assert.Contains(t, output, "msg=\"This is a test message\"")

	ei.Reason = "Unknown"

	// If K8s Event Disable Log
	output = captureLogEntries(func() {
		logger.LogAppProjEvent(&proj, ei, "This is a test message", "")
	})

	assert.Empty(t, output)
}

func TestLogAppEvent(t *testing.T) {
	logger := NewAuditLogger(fake.NewClientset(), _somecomponent, testEnableEventLog)
	assert.NotNil(t, logger)

	app := argoappv1.Application{
		ObjectMeta: metav1.ObjectMeta{
			Name:            "testapp",
			Namespace:       "argocd",
			ResourceVersion: "1",
			UID:             "a-b-c-d-e",
		},
		Spec: argoappv1.ApplicationSpec{
			Destination: argoappv1.ApplicationDestination{
				Server:    "https://127.0.0.1:6443",
				Namespace: "testns",
			},
		},
	}

	ei := EventInfo{
		Reason: _test,
		Type:   "info",
	}

	output := captureLogEntries(func() {
		logger.LogAppEvent(&app, ei, "This is a test message", "", nil)
	})

	assert.Contains(t, output, "level=info")
	assert.Contains(t, output, "application=testapp")
	assert.Contains(t, output, "dest-namespace=testns")
	assert.Contains(t, output, "dest-server=\"https://127.0.0.1:6443\"")
	assert.Contains(t, output, "reason=test")
	assert.Contains(t, output, "type=info")
	assert.Contains(t, output, "msg=\"This is a test message\"")

	ei.Reason = "Unknown"

	// If K8s Event Disable Log
	output = captureLogEntries(func() {
		logger.LogAppEvent(&app, ei, "This is a test message", "", nil)
	})

	assert.Empty(t, output)
}

func TestLogResourceEvent(t *testing.T) {
	logger := NewAuditLogger(fake.NewClientset(), _somecomponent, testEnableEventLog)
	assert.NotNil(t, logger)

	res := argoappv1.ResourceNode{
		ResourceRef: argoappv1.ResourceRef{
			Group:     "argocd.argoproj.io",
			Version:   "v1alpha1",
			Kind:      "SignatureKey",
			Name:      "testapp",
			Namespace: "argocd",
			UID:       "a-b-c-d-e",
		},
	}

	ei := EventInfo{
		Reason: _test,
		Type:   "info",
	}

	output := captureLogEntries(func() {
		logger.LogResourceEvent(&res, ei, "This is a test message", "")
	})

	assert.Contains(t, output, "level=info")
	assert.Contains(t, output, "name=testapp")
	assert.Contains(t, output, "reason=test")
	assert.Contains(t, output, "type=info")
	assert.Contains(t, output, "msg=\"This is a test message\"")

	ei.Reason = "Unknown"

	// If K8s Event Disable Log
	output = captureLogEntries(func() {
		logger.LogResourceEvent(&res, ei, "This is a test message", "")
	})

	assert.Empty(t, output)
}
