package core

import (
	"bytes"
	"context"
	"encoding/gob"
	"fmt"
	"reflect"
	"sort"
	"sync"
	"time"

	"ragflow/internal/harness/core/schema"
)

func init() {
	schema.RegisterType("_harness_event_wrap_entry", func() any { return &eventWrapEntry{} })
}

// eventWrapEntry wraps an event with metadata for checkpoint persistence.
type eventWrapEntry struct {
	Event     any
	Timestamp int64
}

// consumeStream checks if the wrapped event contains a streaming message and, if so,
// fully consumes the stream before checkpoint. This prevents partial data in checkpoints.
func (e *eventWrapEntry) consumeStream() {
	if e.Event == nil {
		return
	}
	ev, ok := e.Event.(*AgentEvent)
	if !ok || ev.Output == nil || ev.Output.MessageOutput == nil {
		return
	}
	mv := ev.Output.MessageOutput
	if !mv.IsStreaming || mv.MessageStream == nil {
		return
	}
	merged, err := schema.ConcatMessageStream(mv.MessageStream)
	if err == nil {
		mv.Message = merged
		mv.IsStreaming = false
		mv.MessageStream = nil
	}
}

func (e *eventWrapEntry) GobEncode() ([]byte, error) {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	if err := enc.Encode(e.Timestamp); err != nil {
		return nil, err
	}
	if e.Event == nil {
		if err := enc.Encode(false); err != nil {
			return nil, err
		}
	} else {
		if err := enc.Encode(true); err != nil {
			return nil, err
		}
		typeName := reflect.TypeOf(e.Event).String()
		// Gob-registered types use their registered name; try direct encode first.
		if err := enc.Encode(&typeName); err != nil {
			return nil, err
		}
		if err := enc.Encode(e.Event); err != nil {
			return nil, fmt.Errorf("gob encode event (%s): %w", typeName, err)
		}
	}
	return buf.Bytes(), nil
}

func (e *eventWrapEntry) GobDecode(data []byte) error {
	buf := bytes.NewBuffer(data)
	dec := gob.NewDecoder(buf)
	if err := dec.Decode(&e.Timestamp); err != nil {
		return err
	}
	var nonNil bool
	if err := dec.Decode(&nonNil); err != nil {
		return err
	}
	if nonNil {
		var typeName string
		if err := dec.Decode(&typeName); err != nil {
			return err
		}
		// Decode into generic interface{} — gob will reconstruct registered types.
		e.Event = new(any)
		if err := dec.Decode(e.Event); err != nil {
			return fmt.Errorf("gob decode event: type %q may not be registered; wrap with schema.RegisterName: %w", typeName, err)
		}
		// Decode into interface{} wraps in a *any; unwrap.
		if p, ok := e.Event.(*any); ok {
			e.Event = *p
		}
	}
	return nil
}

// branchEvents holds per-lane event history for parallel workflows.
// Each parallel branch in a workflow gets its own branchEvents, forming a linked
// list via Parent. Events are collected per-lane and merged chronologically on join.
type branchEvents struct {
	Events []*eventWrapEntry
	Parent *branchEvents
}

// runSession holds per-execution mutable state for an agent run.
type runSession struct {
	mu           sync.Mutex
	Values       map[string]any
	valuesMx     *sync.Mutex
	events       []*eventWrapEntry
	BranchEvents *branchEvents
	TypedEvents  any // *[]*typedAgentEventWrapper[M] for AgenticMessage path (gob-encodable)
}

func newRunSession() *runSession {
	return &runSession{Values: make(map[string]any), valuesMx: &sync.Mutex{}}
}

func (s *runSession) addEvent(event any) {
	entry := &eventWrapEntry{Event: event, Timestamp: time.Now().UnixNano()}
	entry.consumeStream()

	// If in a parallel lane, append to the lane's local event slice (lock-free).
	if s.BranchEvents != nil {
		s.BranchEvents.Events = append(s.BranchEvents.Events, entry)
		return
	}

	// Otherwise, on the main path. Append to shared Events slice (with lock).
	s.mu.Lock()
	s.events = append(s.events, entry)
	s.mu.Unlock()
}

func (s *runSession) getEvents() []any {
	// If there are no in-flight lane events, return the main slice directly.
	if s.BranchEvents == nil {
		s.mu.Lock()
		r := unwrapEvents(s.events)
		s.mu.Unlock()
		return r
	}

	// Collect committed events from main slice.
	s.mu.Lock()
	committed := make([]*eventWrapEntry, len(s.events))
	copy(committed, s.events)
	s.mu.Unlock()

	// Traverse the lane linked list to collect in-flight events.
	var all []*eventWrapEntry
	all = append(all, committed...)
	for lane := s.BranchEvents; lane != nil; lane = lane.Parent {
		all = append(all, lane.Events...)
	}

	// Sort all events by timestamp for chronological order.
	sort.Slice(all, func(i, j int) bool {
		return all[i].Timestamp < all[j].Timestamp
	})

	return unwrapEvents(all)
}

// unwrapEvents extracts the inner Event from eventWrapEntry slice.
func unwrapEvents(entries []*eventWrapEntry) []any {
	r := make([]any, 0, len(entries))
	for _, e := range entries {
		if e != nil {
			r = append(r, e.Event)
		}
	}
	return r
}

// runContext holds runtime metadata for an agent execution.
type runContext struct {
	mu        sync.Mutex
	RootInput any
	RunPath   []RunStep
	Session   *runSession
}

// getRunPath safely returns a copy of RunPath under lock.
func (rc *runContext) getRunPath() []RunStep {
	if rc == nil {
		return nil
	}
	rc.mu.Lock()
	defer rc.mu.Unlock()
	cp := make([]RunStep, len(rc.RunPath))
	copy(cp, rc.RunPath)
	return cp
}

// setRunPath safely replaces RunPath under lock.
func (rc *runContext) setRunPath(v []RunStep) {
	if rc == nil {
		return
	}
	rc.mu.Lock()
	rc.RunPath = v
	rc.mu.Unlock()
}

// appendRunPath safely appends to RunPath under lock.
func (rc *runContext) appendRunPath(v RunStep) {
	if rc == nil {
		return
	}
	rc.mu.Lock()
	rc.RunPath = append(rc.RunPath, v)
	rc.mu.Unlock()
}

type runContextKey struct{}

func ctxWithNewTypedRunCtx[M MessageType](ctx context.Context, input *TypedAgentInput[M], _ bool) context.Context {
	// sharedParentSession parameter is reserved for future use.
	// Currently a new isolated session is always created.
	rc := &runContext{RootInput: input, RunPath: make([]RunStep, 0), Session: newRunSession()}
	return context.WithValue(ctx, runContextKey{}, rc)
}

// initRunCtx initializes or extends a run context and appends the agent name
// to the run path. If a run context already exists in ctx, it is reused — this
// means nested agent calls share the same Session (Values, events) and the
// RunPath accumulates across all agents in the call chain.
func initRunCtx(ctx context.Context, agentName string, input *AgentInput) (context.Context, *runContext) {
	rc := getRunCtx(ctx)
	if rc == nil {
		rc = &runContext{RootInput: input, RunPath: make([]RunStep, 0), Session: newRunSession()}
		ctx = context.WithValue(ctx, runContextKey{}, rc)
	}
	rc.appendRunPath(RunStep{agentName: agentName})
	return ctx, rc
}

func getRunCtx(ctx context.Context) *runContext {
	if v := ctx.Value(runContextKey{}); v != nil {
		return v.(*runContext)
	}
	return nil
}

func setRunCtx(ctx context.Context, rc *runContext) context.Context {
	return context.WithValue(ctx, runContextKey{}, rc)
}

func forkRunCtx(ctx context.Context) context.Context {
	parent := getRunCtx(ctx)
	if parent == nil || parent.Session == nil {
		return ctx
	}

	// Create a new session for the child lane.
	// Share committed history (Events) and values, but give the child its own BranchEvents.
	parent.Session.mu.Lock()
	eventsCopy := make([]*eventWrapEntry, len(parent.Session.events))
	copy(eventsCopy, parent.Session.events)
	parent.Session.mu.Unlock()

	childSession := &runSession{
		events:   eventsCopy,
		Values:   parent.Session.Values, // Share values map
		valuesMx: parent.Session.valuesMx,
	}

	childSession.BranchEvents = &branchEvents{
		Parent: parent.Session.BranchEvents,
		Events: make([]*eventWrapEntry, 0),
	}

	// Create a new runContext for the child, pointing to the new session.
	child := &runContext{
		RootInput: parent.RootInput,
		RunPath:   parent.getRunPath(),
		Session:   childSession,
	}
	return context.WithValue(ctx, runContextKey{}, child)
}

func updateRunPathOnly(ctx context.Context, steps ...string) context.Context {
	rc := getRunCtx(ctx)
	if rc == nil {
		return ctx
	}
	newPath := make([]RunStep, 0, len(steps))
	for _, s := range steps {
		newPath = append(newPath, RunStep{agentName: s})
	}
	rc.setRunPath(newPath)
	return ctx
}

func joinRunCtxs(ctx context.Context, childCtxs ...context.Context) {
	parent := getRunCtx(ctx)
	if parent == nil || parent.Session == nil {
		return
	}

	switch len(childCtxs) {
	case 0:
		return
	case 1:
		// Optimization: single branch, no sorting needed.
		newEvents := unwindLaneEvents(childCtxs...)
		commitEvents(parent, newEvents)
		return
	}

	// Collect events from all child lanes.
	newEvents := unwindLaneEvents(childCtxs...)

	// Sort by timestamp for chronological order.
	sort.Slice(newEvents, func(i, j int) bool {
		return newEvents[i].Timestamp < newEvents[j].Timestamp
	})

	commitEvents(parent, newEvents)
}

// commitEvents appends events to the correct parent lane or main event log.
func commitEvents(rc *runContext, entries []*eventWrapEntry) {
	if rc == nil || rc.Session == nil {
		return
	}
	if rc.Session.BranchEvents != nil {
		// If committing to a lane, append to its event slice.
		rc.Session.BranchEvents.Events = append(rc.Session.BranchEvents.Events, entries...)
	} else {
		// Otherwise, commit to main shared Events slice with lock.
		rc.Session.mu.Lock()
		rc.Session.events = append(rc.Session.events, entries...)
		rc.Session.mu.Unlock()
	}
}

// unwindLaneEvents collects all events from the BranchEvents linked list of the given
// contexts. Traverses the full Parent chain to capture events from deeply forked lanes.
func unwindLaneEvents(ctxs ...context.Context) []*eventWrapEntry {
	var all []*eventWrapEntry
	for _, ctx := range ctxs {
		rc := getRunCtx(ctx)
		if rc == nil || rc.Session == nil {
			continue
		}
		for lane := rc.Session.BranchEvents; lane != nil; lane = lane.Parent {
			all = append(all, lane.Events...)
		}
	}
	return all
}

func getSession(ctx context.Context) *runSession {
	if rc := getRunCtx(ctx); rc != nil {
		return rc.Session
	}
	return nil
}

func AddSessionValues(ctx context.Context, values map[string]any) {
	rc := getRunCtx(ctx)
	if rc == nil || rc.Session == nil || values == nil {
		return
	}
	rc.Session.valuesMx.Lock()
	defer rc.Session.valuesMx.Unlock()
	for k, v := range values {
		rc.Session.Values[k] = v
	}
}
