package oonimkall

import (
	"errors"
	"fmt"
	"io"
	"sync"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/ooni/probe-cli/v3/internal/engine/model"
)

type RecordingLogger struct {
	DebugLogs []string
	InfoLogs  []string
	WarnLogs  []string
	mu        sync.Mutex
}

func (rl *RecordingLogger) Debug(msg string) {
	rl.mu.Lock()
	defer rl.mu.Unlock()
	rl.DebugLogs = append(rl.DebugLogs, msg)
}

func (rl *RecordingLogger) Info(msg string) {
	rl.mu.Lock()
	defer rl.mu.Unlock()
	rl.InfoLogs = append(rl.InfoLogs, msg)
}

func (rl *RecordingLogger) Warn(msg string) {
	rl.mu.Lock()
	defer rl.mu.Unlock()
	rl.WarnLogs = append(rl.WarnLogs, msg)
}

func LoggerEmitMessages(logger model.Logger) {
	logger.Warnf("a formatted warn message: %+v", io.EOF)
	logger.Warn("a warn string")
	logger.Infof("a formatted info message: %+v", io.EOF)
	logger.Info("a info string")
	logger.Debugf("a formatted debug message: %+v", io.EOF)
	logger.Debug("a debug string")
}

func TestNewLoggerNilLogger(t *testing.T) {
	// The objective of this test is to make sure that, even if the
	// Logger instance is nil, we get back something that works, that
	// is, something that does not crash when it is used.
	logger := newLogger(nil, true)
	LoggerEmitMessages(logger)
}

func (rl *RecordingLogger) VerifyNumberOfEntries(debugEntries int) error {
	if len(rl.DebugLogs) != debugEntries {
		return errors.New("unexpected number of debug messages")
	}
	if len(rl.InfoLogs) != 2 {
		return errors.New("unexpected number of info messages")
	}
	if len(rl.WarnLogs) != 2 {
		return errors.New("unexpected number of warn messages")
	}
	return nil
}

func (rl *RecordingLogger) ExpectedEntries(level string) []string {
	return []string{
		fmt.Sprintf("a formatted %s message: %+v", level, io.EOF),
		fmt.Sprintf("a %s string", level),
	}
}

func (rl *RecordingLogger) CheckNonVerboseEntries() error {
	if diff := cmp.Diff(rl.InfoLogs, rl.ExpectedEntries("info")); diff != "" {
		return errors.New(diff)
	}
	if diff := cmp.Diff(rl.WarnLogs, rl.ExpectedEntries("warn")); diff != "" {
		return errors.New(diff)
	}
	return nil
}

func (rl *RecordingLogger) CheckVerboseEntries() error {
	if diff := cmp.Diff(rl.DebugLogs, rl.ExpectedEntries("debug")); diff != "" {
		return errors.New(diff)
	}
	return nil
}

func TestNewLoggerQuietLogger(t *testing.T) {
	handler := new(RecordingLogger)
	logger := newLogger(handler, false)
	LoggerEmitMessages(logger)
	if err := handler.VerifyNumberOfEntries(0); err != nil {
		t.Fatal(err)
	}
	if err := handler.CheckNonVerboseEntries(); err != nil {
		t.Fatal(err)
	}
}

func TestNewLoggerVerboseLogger(t *testing.T) {
	handler := new(RecordingLogger)
	logger := newLogger(handler, true)
	LoggerEmitMessages(logger)
	if err := handler.VerifyNumberOfEntries(2); err != nil {
		t.Fatal(err)
	}
	if err := handler.CheckNonVerboseEntries(); err != nil {
		t.Fatal(err)
	}
	if err := handler.CheckVerboseEntries(); err != nil {
		t.Fatal(err)
	}
}