/*
 * Copyright 2025 CloudWeGo Authors
 *
 * 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
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package adk

import (
	"context"
	"errors"
	"io"
	"strings"

	"github.com/google/uuid"

	"github.com/cloudwego/eino/internal"
	"github.com/cloudwego/eino/schema"
)

type AsyncIterator[T any] struct {
	ch *internal.UnboundedChan[T]
}

func (ai *AsyncIterator[T]) Next() (T, bool) {
	return ai.ch.Receive()
}

type AsyncGenerator[T any] struct {
	ch *internal.UnboundedChan[T]
}

func (ag *AsyncGenerator[T]) Send(v T) {
	ag.ch.Send(v)
}

func (ag *AsyncGenerator[T]) trySend(v T) bool {
	return ag.ch.TrySend(v)
}

func (ag *AsyncGenerator[T]) Close() {
	ag.ch.Close()
}

// NewAsyncIteratorPair returns a paired async iterator and generator
// that share the same underlying channel.
func NewAsyncIteratorPair[T any]() (*AsyncIterator[T], *AsyncGenerator[T]) {
	ch := internal.NewUnboundedChan[T]()
	return &AsyncIterator[T]{ch}, &AsyncGenerator[T]{ch}
}

func copyMap[K comparable, V any](m map[K]V) map[K]V {
	res := make(map[K]V, len(m))
	for k, v := range m {
		res[k] = v
	}
	return res
}

func cloneSlice[T any](s []T) []T {
	if s == nil {
		return nil
	}
	res := make([]T, len(s))
	copy(res, s)
	return res
}

func concatInstructions(instructions ...string) string {
	var sb strings.Builder
	sb.WriteString(instructions[0])
	for i := 1; i < len(instructions); i++ {
		sb.WriteString("\n\n")
		sb.WriteString(instructions[i])
	}

	return sb.String()
}

// GenTransferMessages generates assistant and tool messages to instruct a
// transfer-to-agent tool call targeting the destination agent.
//
// NOT RECOMMENDED: Agent transfer with full context sharing between agents has not proven
// to be more effective empirically. Consider using ChatModelAgent with AgentTool
// or DeepAgent instead for most multi-agent scenarios.
func GenTransferMessages(_ context.Context, destAgentName string) (Message, Message) {
	toolCallID := uuid.NewString()
	tooCall := schema.ToolCall{ID: toolCallID, Function: schema.FunctionCall{Name: TransferToAgentToolName, Arguments: destAgentName}}
	assistantMessage := schema.AssistantMessage("", []schema.ToolCall{tooCall})
	msg := transferToAgentToolOutput(destAgentName)
	toolMessage := schema.ToolMessage(msg, toolCallID, schema.WithToolName(TransferToAgentToolName))
	return assistantMessage, toolMessage
}

func typedSetAutomaticClose[M MessageType](e *TypedAgentEvent[M]) {
	if e.Output == nil || e.Output.MessageOutput == nil || !e.Output.MessageOutput.IsStreaming {
		return
	}

	e.Output.MessageOutput.MessageStream.SetAutomaticClose()
}

// set automatic close for event's message stream
func setAutomaticClose(e *AgentEvent) {
	typedSetAutomaticClose(e)
}

// getMessageFromWrappedEvent extracts the message from an AgentEvent.
// If the stream contains an error chunk, this function returns (nil, err) and
// sets StreamErr to prevent re-consumption. The nil message ensures that
// failed stream responses are not included in subsequent agents' context windows.
func getMessageFromTypedWrappedEvent[M MessageType](e *typedAgentEventWrapper[M]) (M, error) {
	var zero M
	if e.event.Output == nil || e.event.Output.MessageOutput == nil {
		return zero, nil
	}

	if !e.event.Output.MessageOutput.IsStreaming {
		return e.event.Output.MessageOutput.Message, nil
	}

	if e.StreamErr != nil {
		return zero, e.StreamErr
	}

	if !isNilMessage(e.concatenatedMessage) {
		return e.concatenatedMessage, nil
	}

	e.consumeStream()

	if e.StreamErr != nil {
		return zero, e.StreamErr
	}
	return e.concatenatedMessage, nil
}

func getMessageFromWrappedEvent(e *agentEventWrapper) (Message, error) {
	if e.AgentEvent.Output == nil || e.AgentEvent.Output.MessageOutput == nil {
		return nil, nil
	}

	if !e.AgentEvent.Output.MessageOutput.IsStreaming {
		return e.AgentEvent.Output.MessageOutput.Message, nil
	}

	if e.StreamErr != nil {
		return nil, e.StreamErr
	}

	if e.concatenatedMessage != nil {
		return e.concatenatedMessage, nil
	}

	e.consumeStream()

	if e.StreamErr != nil {
		return nil, e.StreamErr
	}
	return e.concatenatedMessage, nil
}

// consumeStream drains the message stream, setting concatenatedMessage on
// success or StreamErr on failure. The stream is always replaced with an
// error-free, materialized version safe for gob encoding.
// Must be called at most once (guarded by callers checking concatenatedMessage/StreamErr).
func (e *agentEventWrapper) consumeStream() {
	e.mu.Lock()
	defer e.mu.Unlock()

	if e.concatenatedMessage != nil {
		return
	}

	s := e.AgentEvent.Output.MessageOutput.MessageStream
	var msgs []Message

	defer s.Close()
	for {
		msg, err := s.Recv()
		if err != nil {
			if err == io.EOF {
				break
			}
			e.StreamErr = err
			e.AgentEvent.Output.MessageOutput.MessageStream = schema.StreamReaderFromArray(msgs)
			return
		}
		msgs = append(msgs, msg)
	}

	if len(msgs) == 0 {
		e.StreamErr = errors.New("no messages in MessageVariant.MessageStream")
		// Defensively replace the stream. The defer s.Close() above already
		// ensures subsequent Recv() returns io.EOF, but we replace it anyway
		// to make the invariant explicit: after consumeStream, MessageStream
		// is always safe for MessageVariant.GobEncode to consume.
		e.AgentEvent.Output.MessageOutput.MessageStream = schema.StreamReaderFromArray(msgs)
		return
	}

	if len(msgs) == 1 {
		e.concatenatedMessage = msgs[0]
	} else {
		var err error
		e.concatenatedMessage, err = schema.ConcatMessages(msgs)
		if err != nil {
			e.StreamErr = err
			e.AgentEvent.Output.MessageOutput.MessageStream = schema.StreamReaderFromArray(msgs)
			return
		}
	}

	e.AgentEvent.Output.MessageOutput.MessageStream = schema.StreamReaderFromArray([]Message{e.concatenatedMessage})
}

// copyTypedAgentEvent copies a TypedAgentEvent.
// If the MessageVariant is streaming, the MessageStream will be copied.
// RunPath will be deep copied.
// The result of Copy will be a new TypedAgentEvent that is:
// - safe to set fields of TypedAgentEvent
// - safe to extend RunPath
// - safe to receive from MessageStream
// NOTE: even if the event is copied, it's still not recommended to modify
// the Message itself or Chunks of the MessageStream, as they are not copied.
// NOTE: if you have CustomizedOutput or CustomizedAction, they are NOT copied.
func copyTypedAgentEvent[M MessageType](ae *TypedAgentEvent[M]) *TypedAgentEvent[M] {
	rp := make([]RunStep, len(ae.RunPath))
	copy(rp, ae.RunPath)

	copied := &TypedAgentEvent[M]{
		AgentName: ae.AgentName,
		RunPath:   rp,
		Action:    ae.Action,
		Err:       ae.Err,
	}

	if ae.Output == nil {
		return copied
	}

	copied.Output = &TypedAgentOutput[M]{
		CustomizedOutput: ae.Output.CustomizedOutput,
	}

	mv := ae.Output.MessageOutput
	if mv == nil {
		return copied
	}

	copied.Output.MessageOutput = &TypedMessageVariant[M]{
		IsStreaming: mv.IsStreaming,
		Role:        mv.Role,
		AgenticRole: mv.AgenticRole,
		ToolName:    mv.ToolName,
	}
	if mv.IsStreaming {
		sts := ae.Output.MessageOutput.MessageStream.Copy(2)
		mv.MessageStream = sts[0]
		copied.Output.MessageOutput.MessageStream = sts[1]
	} else {
		copied.Output.MessageOutput.Message = mv.Message
	}

	return copied
}

// TypedGetMessage extracts the message from a TypedAgentEvent, concatenating a stream if present.
func TypedGetMessage[M MessageType](e *TypedAgentEvent[M]) (M, *TypedAgentEvent[M], error) {
	var zero M
	if e.Output == nil || e.Output.MessageOutput == nil {
		return zero, e, nil
	}

	msgOutput := e.Output.MessageOutput
	if msgOutput.IsStreaming {
		ss := msgOutput.MessageStream.Copy(2)
		e.Output.MessageOutput.MessageStream = ss[0]

		msg, err := concatMessageStream(ss[1])

		return msg, e, err
	}

	return msgOutput.Message, e, nil
}

// GetMessage extracts the Message from an AgentEvent. For streaming output,
// it duplicates the stream and concatenates it into a single Message.
func GetMessage(e *AgentEvent) (Message, *AgentEvent, error) {
	return TypedGetMessage(e)
}

func typedErrorIter[M MessageType](err error) *AsyncIterator[*TypedAgentEvent[M]] {
	iterator, generator := NewAsyncIteratorPair[*TypedAgentEvent[M]]()
	generator.Send(&TypedAgentEvent[M]{Err: err})
	generator.Close()
	return iterator
}

func genErrorIter(err error) *AsyncIterator[*AgentEvent] {
	return typedErrorIter[*schema.Message](err)
}
