// Copyright (C) MongoDB, Inc. 2023-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

package unified

import (
	"context"
	"sync"

	"go.mongodb.org/mongo-driver/internal/logger"
)

// orderedLogMessage is a logMessage with an "order" field representing the
// order in which the log message was observed.
type orderedLogMessage struct {
	*logMessage
	order int
}

// Logger is the Sink used to captured log messages for logger verification in
// the unified spec tests.
type Logger struct {
	// bufSize is the number of logs expected to be sent to the logger for a
	// unified spec test.
	bufSize int

	// lastOrder increments each time the "Info" method is called, and is used to
	// determine when to close the logQueue.
	lastOrder int

	// orderMu guards the order value, which increments each time the "Info"
	// method is called. This is necessary since "Info" could be called from
	// multiple go routines, e.g. SDAM logs.
	orderMu        sync.RWMutex
	logQueue       chan orderedLogMessage
	ignoreMessages []*logMessage
}

func newLogger(olm *observeLogMessages, bufSize int, ignoreMessages []*logMessage) *Logger {
	if olm == nil {
		return nil
	}

	return &Logger{
		lastOrder:      1,
		logQueue:       make(chan orderedLogMessage, bufSize),
		bufSize:        bufSize,
		ignoreMessages: ignoreMessages,
	}
}

// Info implements the logger.Sink interface's "Info" method for printing log
// messages.
func (log *Logger) Info(level int, msg string, args ...interface{}) {
	log.orderMu.Lock()
	defer log.orderMu.Unlock()

	if log.logQueue == nil {
		return
	}

	// If the order is greater than the buffer size, we must return. This
	// would indicate that the logQueue channel has been closed.
	if log.lastOrder > log.bufSize {
		return
	}

	// Add the Diff back to the level, as there is no need to create a
	// logging offset.
	level += logger.DiffToInfo

	logMessage, err := newLogMessage(level, msg, args...)
	if err != nil {
		panic(err)
	}

	for _, ignoreMessage := range log.ignoreMessages {
		if err := verifyLogMatch(context.Background(), ignoreMessage, logMessage); err == nil {
			return
		}
	}

	defer func() { log.lastOrder++ }()

	// Send the log message to the "orderedLogMessage" channel for
	// validation.
	log.logQueue <- orderedLogMessage{
		order:      log.lastOrder + 1,
		logMessage: logMessage}

	// If the order has reached the buffer size, then close the channel.
	if log.lastOrder == log.bufSize {
		close(log.logQueue)
	}
}

// Error implements the logger.Sink interface's "Error" method for printing log
// errors. In this case, if an error occurs we will simply treat it as
// informational.
func (log *Logger) Error(err error, msg string, args ...interface{}) {
	args = append(args, "error", err)
	log.Info(int(logger.LevelInfo), msg, args)
}
