package logging

import (
	"bytes"
	"context"
	"crypto/rand"
	"net/url"
	"strings"
	"testing"

	"github.com/open-policy-agent/opa/internal/uuid"
)

func TestWithFields(t *testing.T) {
	logger := New().WithFields(map[string]interface{}{"context": "contextvalue"})

	var fieldvalue interface{}
	var ok bool

	if fieldvalue, ok = logger.(*StandardLogger).fields["context"]; !ok {
		t.Fatal("Logger did not contain configured field")
	}

	if fieldvalue.(string) != "contextvalue" {
		t.Fatal("Logger did not contain configured field value")
	}
}

func TestCaptureWarningWithErrorSet(t *testing.T) {
	buf := bytes.Buffer{}
	logger := New()
	logger.SetOutput(&buf)
	logger.SetLevel(Error)

	logger.Warn("This is a warning. Next time, I won't compile.")
	logger.Error("Fix your issues. I'm not compiling.")

	expected := []string{
		`level=warning msg="This is a warning. Next time, I won't compile."`,
		`level=error msg="Fix your issues. I'm not compiling."`,
	}
	for _, exp := range expected {
		if !strings.Contains(buf.String(), exp) {
			t.Errorf("expected string %q not found in logs", exp)
		}
	}
}

func TestNoFormattingForSingleString(t *testing.T) {
	buf := bytes.Buffer{}
	logger := New()
	logger.SetOutput(&buf)
	logger.SetLevel(Debug)

	// NOTE(sr): This construction is somewhat realistic: If we fed logger.Error()
	// a format string but no args, the golang linters would yell. The indirection
	// taken here is enough to not trigger linters.
	x := url.PathEscape("/foo/bar/bar")
	logger.Debug(x) //nolint:govet
	logger.Info(x)  //nolint:govet
	logger.Warn(x)  //nolint:govet
	logger.Error(x) //nolint:govet

	exp := `"%2Ffoo%2Fbar%2Fbar"`
	expected := []string{
		`level=error msg=` + exp,
		`level=warning msg=` + exp,
		`level=info msg=` + exp,
		`level=debug msg=` + exp,
	}
	for _, exp := range expected {
		if !strings.Contains(buf.String(), exp) {
			t.Errorf("expected string %q not found in logs", exp)
		}
	}
	if t.Failed() {
		t.Logf("actual output:\n%s", buf.String())
	}
}

func TestWithFieldsOverrides(t *testing.T) {
	logger := New().
		WithFields(map[string]interface{}{"context": "contextvalue"}).
		WithFields(map[string]interface{}{"context": "changedcontextvalue"})

	var fieldvalue interface{}
	var ok bool

	if fieldvalue, ok = logger.(*StandardLogger).fields["context"]; !ok {
		t.Fatal("Logger did not contain configured field")
	}

	if fieldvalue.(string) != "changedcontextvalue" {
		t.Fatal("Logger did not contain configured field value")
	}
}

func TestWithFieldsMerges(t *testing.T) {
	logger := New().
		WithFields(map[string]interface{}{"context": "contextvalue"}).
		WithFields(map[string]interface{}{"anothercontext": "anothercontextvalue"})

	var fieldvalue interface{}
	var ok bool

	if fieldvalue, ok = logger.(*StandardLogger).fields["context"]; !ok {
		t.Fatal("Logger did not contain configured field")
	}

	if fieldvalue.(string) != "contextvalue" {
		t.Fatal("Logger did not contain configured field value")
	}

	if fieldvalue, ok = logger.(*StandardLogger).fields["anothercontext"]; !ok {
		t.Fatal("Logger did not contain configured field")
	}

	if fieldvalue.(string) != "anothercontextvalue" {
		t.Fatal("Logger did not contain configured field value")
	}
}

func TestRequestContextFields(t *testing.T) {
	fields := RequestContext{
		ClientAddr: "127.0.0.1",
		ReqID:      1,
		ReqMethod:  "GET",
		ReqPath:    "/test",
	}.Fields()

	var fieldvalue interface{}
	var ok bool

	if fieldvalue, ok = fields["client_addr"]; !ok {
		t.Fatal("Fields did not contain the client_addr field")
	}

	if fieldvalue.(string) != "127.0.0.1" {
		t.Fatal("Fields did not contain the configured client_addr value")
	}

	if fieldvalue, ok = fields["req_id"]; !ok {
		t.Fatal("Fields did not contain the req_id field")
	}

	if fieldvalue.(uint64) != 1 {
		t.Fatal("Fields did not contain the configured req_id value")
	}

	if fieldvalue, ok = fields["req_method"]; !ok {
		t.Fatal("Fields did not contain the req_method field")
	}

	if fieldvalue.(string) != "GET" {
		t.Fatal("Fields did not contain the configured req_method value")
	}

	if fieldvalue, ok = fields["req_path"]; !ok {
		t.Fatal("Fields did not contain the req_path field")
	}

	if fieldvalue.(string) != "/test" {
		t.Fatal("Fields did not contain the configured req_path value")
	}
}

func TestDecsionIDFromContext(t *testing.T) {
	id, err := uuid.New(rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	ctx := WithDecisionID(context.Background(), id)

	act, ok := DecisionIDFromContext(ctx)
	if !ok {
		t.Fatalf("expected 'ok' to be true")
	}
	if exp := id; act != exp {
		t.Errorf("Expected %q to be %q", act, exp)
	}
}
