package context

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

	jsoniter "github.com/json-iterator/go"
	"github.com/yaoapp/gou/plan"
	"github.com/yaoapp/kun/log"
	"github.com/yaoapp/yao/agent/output/message"
	"github.com/yaoapp/yao/config"
	"github.com/yaoapp/yao/openapi/oauth/types"
	"github.com/yaoapp/yao/trace"
	traceTypes "github.com/yaoapp/yao/trace/types"
)

// Global context registry for interrupt management
var (
	contextRegistry = &sync.Map{} // map[contextID]*Context
)

// New create a new context with basic initialization
func New(parent context.Context, authorized *types.AuthorizedInfo, chatID string) *Context {
	if parent == nil {
		parent = context.Background()
	}

	ctx := &Context{
		Context:         parent,
		ID:              generateContextID(), // Generate unique ID for the context
		Authorized:      authorized,          // Set authorized info
		Space:           plan.NewMemorySharedSpace(),
		ChatID:          chatID,
		IDGenerator:     message.NewIDGenerator(),  // Initialize ID generator for this context
		messageMetadata: newMessageMetadataStore(), // Initialize message metadata store
	}

	return ctx
}

// NewWithPayload create a new context and unmarshal from payload
func NewWithPayload(parent context.Context, authorized *types.AuthorizedInfo, chatID, payload string) *Context {
	ctx := New(parent, authorized, chatID)

	if payload != "" {
		err := jsoniter.Unmarshal([]byte(payload), ctx)
		if err != nil {
			log.Error("%s", err.Error())
		}
	}

	return ctx
}

// NewWithCancel create a new context with cancel
func NewWithCancel(parent context.Context, authorized *types.AuthorizedInfo, chatID string) (*Context, context.CancelFunc) {
	ctx := New(parent, authorized, chatID)
	return WithCancel(ctx)
}

// NewWithTimeout create a new context with timeout
func NewWithTimeout(parent context.Context, authorized *types.AuthorizedInfo, chatID string, timeout time.Duration) (*Context, context.CancelFunc) {
	ctx := New(parent, authorized, chatID)
	return WithTimeout(ctx, timeout)
}

// WithCancel create a new context
func WithCancel(parent *Context) (*Context, context.CancelFunc) {
	new, cancel := context.WithCancel(parent.Context)
	parent.Context = new
	return parent, cancel
}

// WithTimeout create a new context
func WithTimeout(parent *Context, timeout time.Duration) (*Context, context.CancelFunc) {
	new, cancel := context.WithTimeout(parent.Context, timeout)
	parent.Context = new
	return parent, cancel
}

// Release the context and clean up all resources including stacks and trace
func (ctx *Context) Release() {
	log.Trace("[RELEASE] Context cleanup started: contextID=%s, assistantID=%s", ctx.ID, ctx.AssistantID)

	// Unregister from global registry
	if ctx.ID != "" {
		Unregister(ctx.ID)
	}

	// Stop interrupt controller
	if ctx.Interrupt != nil {
		log.Trace("[RELEASE] Stopping interrupt controller")
		ctx.Interrupt.Stop()
		ctx.Interrupt = nil
	}

	// Complete and release trace if exists
	if ctx.trace != nil && ctx.Stack != nil && ctx.Stack.TraceID != "" {
		log.Trace("[RELEASE] Releasing trace: traceID=%s", ctx.Stack.TraceID)

		// Check if context is cancelled - if so, mark as cancelled instead of complete
		if ctx.Context != nil && ctx.Context.Err() != nil {
			log.Trace("[RELEASE] Context cancelled, marking trace as cancelled: err=%v", ctx.Context.Err())

			// Mark trace as cancelled (saves to disk and broadcasts to subscribers)
			log.Trace("[RELEASE] Calling trace.MarkCancelled: traceID=%s", ctx.Stack.TraceID)
			if err := trace.MarkCancelled(ctx.Stack.TraceID, ctx.Context.Err().Error()); err != nil {
				log.Trace("[RELEASE] Failed to mark trace as cancelled: %v", err)
			} else {
				log.Trace("[RELEASE] Successfully marked trace as cancelled")
			}

			// Release trace from registry
			// Subscribers will be notified via channel close and will cleanup automatically
			log.Trace("[RELEASE] Calling trace.Release: traceID=%s", ctx.Stack.TraceID)
			if err := trace.Release(ctx.Stack.TraceID); err != nil {
				log.Trace("[RELEASE] Failed to release trace from registry: %v", err)
			} else {
				log.Trace("[RELEASE] Successfully released trace from registry")
			}
		} else {
			// Normal case: mark complete then release
			if err := ctx.trace.MarkComplete(); err != nil {
				log.Trace("[RELEASE] Failed to mark trace complete: %v", err)
			}

			if err := trace.Release(ctx.Stack.TraceID); err != nil {
				log.Trace("[RELEASE] Failed to release trace: %v", err)
			}
		}

		ctx.trace = nil
	} else {
		log.Trace("[RELEASE] No trace to release (trace=%v, stack=%v)", ctx.trace != nil, ctx.Stack != nil)
	}

	// Clear space
	if ctx.Space != nil {
		log.Trace("[RELEASE] Clearing space")
		ctx.Space.Clear()
		ctx.Space = nil
	}

	// Clear stacks
	if ctx.Stacks != nil {
		log.Trace("[RELEASE] Clearing %d stacks", len(ctx.Stacks))
		for k := range ctx.Stacks {
			delete(ctx.Stacks, k)
		}
		ctx.Stacks = nil
	}

	// Clear current stack reference
	ctx.Stack = nil

	// Clear writer reference
	ctx.Writer = nil

	log.Trace("[RELEASE] Context cleanup completed: contextID=%s", ctx.ID)
	ctx = nil
}

// Send sends data to the context's writer
// This is used by the output module to send messages to the client
// func (ctx *Context) Send(data []byte) error {
// 	if ctx.Writer == nil {
// 		return nil // No writer, silently ignore
// 	}

// 	_, err := ctx.Writer.Write(data)
// 	return err
// }

// Trace returns the trace manager for this context, lazily initialized on first call
// Uses the TraceID from ctx.Stack if available, or generates a new one
func (ctx *Context) Trace() (traceTypes.Manager, error) {
	// Return trace if already initialized
	if ctx.trace != nil {
		return ctx.trace, nil
	}

	// Get TraceID from Stack or generate new one
	var traceID string
	if ctx.Stack != nil && ctx.Stack.TraceID != "" {
		traceID = ctx.Stack.TraceID

		// Try to load existing trace first
		manager, err := trace.Load(traceID)
		if err == nil {
			// Found in registry, reuse it
			ctx.trace = manager
			return manager, nil
		}
	}

	// Get trace configuration from global config
	cfg := config.Conf

	// Prepare driver options
	var driverOptions []any
	var driverType string

	switch cfg.Trace.Driver {
	case "store":
		driverType = trace.Store
		if cfg.Trace.Store == "" {
			return nil, fmt.Errorf("trace store ID not configured")
		}
		driverOptions = []any{cfg.Trace.Store, cfg.Trace.Prefix}

	case "local", "":
		driverType = trace.Local
		driverOptions = []any{cfg.Trace.Path}

	default:
		return nil, fmt.Errorf("unsupported trace driver: %s", cfg.Trace.Driver)
	}

	// Prepare trace options
	traceOption := &traceTypes.TraceOption{ID: traceID, AutoArchive: config.Conf.Mode == "production"}

	// Set trace options from authorized information
	if ctx.Authorized != nil {
		traceOption.CreatedBy = ctx.Authorized.UserID
		traceOption.TeamID = ctx.Authorized.TeamID
		traceOption.TenantID = ctx.Authorized.TenantID
	}

	// Create trace using trace.New (handles registry)
	createdTraceID, manager, err := trace.New(ctx.Context, driverType, traceOption, driverOptions...)
	if err != nil {
		return nil, fmt.Errorf("failed to create trace: %w", err)
	}

	// Update Stack with the created TraceID if needed
	if ctx.Stack != nil && ctx.Stack.TraceID == "" {
		ctx.Stack.TraceID = createdTraceID
	}

	// Store for future calls
	ctx.trace = manager

	return manager, nil
}

// Map the context to a map
func (ctx *Context) Map() map[string]interface{} {
	data := map[string]interface{}{}

	// Authorized information
	if ctx.Authorized != nil {
		data["authorized"] = ctx.Authorized
	}
	if ctx.ChatID != "" {
		data["chat_id"] = ctx.ChatID
	}
	if ctx.AssistantID != "" {
		data["assistant_id"] = ctx.AssistantID
	}
	if ctx.Connector != "" {
		data["connector"] = ctx.Connector
	}
	if ctx.Search != nil {
		data["search"] = *ctx.Search
	}

	// Arguments for call
	if len(ctx.Args) > 0 {
		data["args"] = ctx.Args
	}
	if ctx.Retry {
		data["retry"] = ctx.Retry
	}
	if ctx.RetryTimes > 0 {
		data["retry_times"] = ctx.RetryTimes
	}

	// Locale information
	if ctx.Locale != "" {
		data["locale"] = ctx.Locale
	}
	if ctx.Theme != "" {
		data["theme"] = ctx.Theme
	}

	// Request information
	if ctx.Client.Type != "" || ctx.Client.UserAgent != "" || ctx.Client.IP != "" {
		data["client"] = map[string]interface{}{
			"type":       ctx.Client.Type,
			"user_agent": ctx.Client.UserAgent,
			"ip":         ctx.Client.IP,
		}
	}
	if ctx.Referer != "" {
		data["referer"] = ctx.Referer
	}
	if ctx.Accept != "" {
		data["accept"] = ctx.Accept
	}

	// CUI Context information
	if ctx.Route != "" {
		data["route"] = ctx.Route
	}
	if len(ctx.Metadata) > 0 {
		data["metadata"] = ctx.Metadata
	}

	return data
}

// Global Registry Functions
// ===================================

// Register registers a context to the global registry
func Register(ctx *Context) error {
	if ctx == nil {
		return fmt.Errorf("context is nil")
	}

	if ctx.ID == "" {
		return fmt.Errorf("context ID is empty")
	}

	contextRegistry.Store(ctx.ID, ctx)
	return nil
}

// Unregister removes a context from the global registry
func Unregister(contextID string) {
	contextRegistry.Delete(contextID)
}

// Get retrieves a context from the global registry by ID
func Get(contextID string) (*Context, error) {
	value, ok := contextRegistry.Load(contextID)
	if !ok {
		return nil, fmt.Errorf("context not found: %s", contextID)
	}

	ctx, ok := value.(*Context)
	if !ok {
		return nil, fmt.Errorf("invalid context type")
	}

	return ctx, nil
}

// SendInterrupt sends an interrupt signal to a context by ID
// This is the main entry point for external interrupt requests
func SendInterrupt(contextID string, signal *InterruptSignal) error {
	ctx, err := Get(contextID)
	if err != nil {
		return err
	}

	if ctx.Interrupt == nil {
		return fmt.Errorf("interrupt controller not initialized for context: %s", contextID)
	}

	return ctx.Interrupt.SendSignal(signal)
}

// generateContextID generates a unique context ID
func generateContextID() string {
	return fmt.Sprintf("ctx-%d", time.Now().UnixNano())
}

// RequestID returns the request ID for the context
func (ctx *Context) RequestID() string {
	return fmt.Sprintf("%s", ctx.ID)
}

// TraceID returns the trace ID for the context
func (ctx *Context) TraceID() string {
	if ctx.Stack != nil {
		return ctx.Stack.TraceID
	}
	return ""
}

// recordMessageMetadata records metadata for a sent message
// Used to inherit BlockID and ThreadID in subsequent delta operations
func (ctx *Context) recordMessageMetadata(msg *message.Message) {
	if msg.MessageID == "" || ctx.messageMetadata == nil {
		return
	}

	ctx.messageMetadata.setMessage(msg.MessageID, &MessageMetadata{
		MessageID: msg.MessageID,
		BlockID:   msg.BlockID,
		ThreadID:  msg.ThreadID,
		Type:      msg.Type,
	})
}

// getMessageMetadata retrieves metadata for a message by ID
// Returns nil if message metadata is not found
func (ctx *Context) getMessageMetadata(messageID string) *MessageMetadata {
	if ctx.messageMetadata == nil {
		return nil
	}
	return ctx.messageMetadata.getMessage(messageID)
}
