package actor_test

import (
	"context"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/lightningnetwork/lnd/actor"
	"github.com/lightningnetwork/lnd/fn/v2"
)

// LogMsg is a message type for the TellOnly example.
type LogMsg struct {
	actor.BaseMessage
	Text string
}

// MessageType implements actor.Message.
func (m LogMsg) MessageType() string { return "LogMsg" }

// LoggerActorBehavior is a simple actor behavior that logs messages. It doesn't
// produce a meaningful response for Ask, so it's a good candidate for TellOnly
// interactions.
type LoggerActorBehavior struct {
	mu      sync.Mutex
	logs    []string
	actorID string
}

func NewLoggerActorBehavior(id string) *LoggerActorBehavior {
	return &LoggerActorBehavior{actorID: id}
}

// Receive processes LogMsg messages by appending them to an internal log. The
// response type is 'any' as it's not typically used with Ask.
func (l *LoggerActorBehavior) Receive(ctx context.Context,
	msg actor.Message) fn.Result[any] {

	logMessage, ok := msg.(LogMsg)
	if !ok {
		return fn.Err[any](fmt.Errorf("unexpected message "+
			"type: %s", msg.MessageType()))
	}

	l.mu.Lock()
	defer l.mu.Unlock()

	entry := fmt.Sprintf("[%s from %s]: %s", time.Now().Format("15:04:05"),
		l.actorID, logMessage.Text)
	l.logs = append(l.logs, entry)

	// For Tell, the result is often ignored, but we must return something.
	return fn.Ok[any](nil)
}

func (l *LoggerActorBehavior) GetLogs() []string {
	l.mu.Lock()
	defer l.mu.Unlock()

	copiedLogs := make([]string, len(l.logs))
	copy(copiedLogs, l.logs)

	return copiedLogs
}

// ExampleTellOnlyRef demonstrates using a TellOnlyRef for fire-and-forget
// messaging with an actor.
func ExampleTellOnlyRef() {
	system := actor.NewActorSystem()
	defer system.Shutdown()

	// The logger actor doesn't really have a response type for Ask, so we
	// use 'any'.
	loggerServiceKey := actor.NewServiceKey[actor.Message, any](
		"tell-only-logger-service",
	)

	actorID := "my-logger"
	loggerLogic := NewLoggerActorBehavior(actorID)

	// Spawn the actor.
	fullRef, err := loggerServiceKey.Spawn(system, actorID, loggerLogic)
	if err != nil {
		fmt.Printf("Failed to spawn actor: %v\n", err)
		return
	}
	fmt.Printf("Actor %s spawned.\n", fullRef.ID())

	// Get a TellOnlyRef for the actor. We can get this from the Actor
	// instance itself if we had it, or by type assertion if we know the
	// underlying ref supports it. Since fullRef is ActorRef[actor.Message,
	// any], it already satisfies TellOnlyRef[actor.Message].
	//
	// Or, if we had the *Actor instance: tellOnlyLogger =
	// actorInstance.TellRef()
	var tellOnlyLogger actor.TellOnlyRef[actor.Message] = fullRef

	fmt.Printf("Obtained TellOnlyRef for %s.\n", tellOnlyLogger.ID())

	// Send messages using Tell.
	tellOnlyLogger.Tell(
		context.Background(), LogMsg{Text: "First log entry."},
	)
	tellOnlyLogger.Tell(
		context.Background(), LogMsg{Text: "Second log entry."},
	)

	// Allow some time for messages to be processed.
	time.Sleep(10 * time.Millisecond)

	// Retrieve logs directly from the behavior for verification in this
	// example. In a real scenario, this might not be possible or desired.
	logs := loggerLogic.GetLogs()
	fmt.Println("Logged entries:")
	for _, entry := range logs {
		// Strip the timestamp and actor ID for consistent example
		// output. Example entry: "[15:04:05 from my-logger]: Actual log
		// text"
		parts := strings.SplitN(entry, "]: ", 2)
		if len(parts) == 2 {
			fmt.Println(parts[1])
		}
	}

	// Attempting to Ask using tellOnlyLogger would be a compile-time error:
	// tellOnlyLogger.Ask(context.Background(), LogMsg{Text: "This would
	// fail"})

	// Output:
	// Actor my-logger spawned.
	// Obtained TellOnlyRef for my-logger.
	// Logged entries:
	// First log entry.
	// Second log entry.
}
