// Hacks to let Go log things in Rust.

package main

import (
	"fmt"
	"net"
	"unsafe"
)

// #include <stdint.h>
// void *malloc(size_t size);
// void free(void *ptr);
// typedef void (*logging_callback)(uint8_t, const char*);
// static void log_helper(logging_callback cb, uint8_t log_level, const char *message) { cb(log_level, message); }
import "C"

var loggingCallback C.logging_callback

const (
	levelDebug = 1
	levelInfo  = 2
	levelWarn  = 3
	levelError = 4
)

//export LoggingTest
func LoggingTest(cb C.logging_callback) {
	SetLoggingCallback(cb)
	Log(42, "example: %s", "lol")
}

//export SetLoggingCallback
func SetLoggingCallback(cb C.logging_callback) {
	loggingCallback = cb
}

func Log(level uint8, message string, a ...any) {
	logStr := fmt.Sprintf(message, a...)
	logStrC := C.CString(logStr)
	defer C.free(unsafe.Pointer(logStrC))
	C.log_helper(loggingCallback, C.uchar(level), logStrC)
}

// The stuff below here is copied from lyrebird's logging functions and does
// unsafe logging stuff.

const elidedAddr = "[scrubbed]"

// ElideAddr transforms the string representation of the provided address based
// on the unsafeLogging setting.  Callers that wish to log IP addreses should
// use ElideAddr to sanitize the contents first.
func ElideAddr(addrStr string) string {
	// Only scrub off the address so that it's easier to track connections
	// in logs by looking at the port.
	if _, port, err := net.SplitHostPort(addrStr); err == nil {
		return elidedAddr + ":" + port
	}
	return elidedAddr
}

// ElideError transforms the string representation of the provided error
// based on the unsafeLogging setting.  Callers that wish to log errors
// returned from Go's net package should use ElideError to sanitize the
// contents first.
func ElideError(err error) string {
	// Go's net package is somewhat rude and includes IP address and port
	// information in the string representation of net.Errors.  Figure out if
	// this is the case here, and sanitize the error messages as needed.

	// If err is not a net.Error, just return the string representation,
	// presumably transport authors know what they are doing.
	netErr, ok := err.(net.Error)
	if !ok {
		return err.Error()
	}

	switch t := netErr.(type) {
	case *net.AddrError:
		return t.Err + " " + elidedAddr
	case *net.DNSError:
		return "lookup " + elidedAddr + " on " + elidedAddr + ": " + t.Err
	case *net.InvalidAddrError:
		return "invalid address error"
	case *net.UnknownNetworkError:
		return "unknown network " + elidedAddr
	case *net.OpError:
		return t.Op + ": " + t.Err.Error()
	default:
		// For unknown error types, do the conservative thing and only log the
		// type of the error instead of assuming that the string representation
		// does not contain sensitive information.
		return fmt.Sprintf("network error: <%T>", t)
	}
}
