package agentboot

import (
	"context"
	"strings"
	"time"

	"github.com/tingly-dev/tingly-box/agentboot/common"
	agentsession "github.com/tingly-dev/tingly-box/agentboot/session"
)

// AgentType defines the supported agent types
type AgentType string

const (
	AgentTypeClaude    AgentType = "claude"
	AgentTypeMockAgent AgentType = "mock" // Mock agent for testing
	// AgentTypeCodex  AgentType = "codex"  // Future
	// AgentTypeGemini AgentType = "gemini" // Future
	// AgentTypeCursor AgentType = "cursor" // Future
)

// String returns the string representation of AgentType
func (t AgentType) String() string {
	return string(t)
}

// OutputFormat defines agent output format
type OutputFormat string

const (
	OutputFormatText       OutputFormat = "text"
	OutputFormatStreamJSON OutputFormat = "stream-json"
)

// String returns the string representation of OutputFormat
func (f OutputFormat) String() string {
	return string(f)
}

// PermissionMode defines how permission requests are handled.
// Use ask.Mode for the ask-subsystem-specific mode values.
type PermissionMode string

const (
	PermissionModeAuto   PermissionMode = "auto"   // Auto-approve all requests
	PermissionModeManual PermissionMode = "manual" // Require user approval
	PermissionModeSkip   PermissionMode = "skip"   // Skip permission prompts
)

// String returns the string representation of PermissionMode
func (m PermissionMode) String() string {
	return string(m)
}

// MessageHandler is the primary interface for handling agent callbacks
// This interface is defined here to avoid circular dependencies
type MessageHandler interface {
	OnMessage(msg interface{}) error
	OnError(err error)
	OnComplete(result *CompletionResult)
	OnApproval(ctx context.Context, req PermissionRequest) (PermissionResult, error)
	OnAsk(ctx context.Context, req AskRequest) (AskResult, error)
}

// MessageStreamer handles streaming messages (subset of MessageHandler)
type MessageStreamer interface {
	OnMessage(msg interface{}) error
	OnError(err error)
}

// ApprovalHandler handles permission confirmations
type ApprovalHandler interface {
	OnApproval(ctx context.Context, req PermissionRequest) (PermissionResult, error)
}

// AskHandler handles user questions/selections
type AskHandler interface {
	OnAsk(ctx context.Context, req AskRequest) (AskResult, error)
}

// CompletionCallback handles completion notification
type CompletionCallback interface {
	OnComplete(result *CompletionResult)
}

// AskRequest represents a request to ask the user something
// This is a simplified version of ask.Request to avoid circular imports
type AskRequest struct {
	ID   string `json:"id"`
	Type string `json:"type"` // "permission", "question", "confirmation", "text_input"

	Platform  string `json:"platform"`
	ChatID    string `json:"chat_id"`
	BotUUID   string `json:"bot_uuid"`
	SessionID string `json:"session_id,omitempty"`

	AgentType AgentType              `json:"agent_type"`
	ToolName  string                 `json:"tool_name,omitempty"`
	Input     map[string]interface{} `json:"input,omitempty"`
	Message   string                 `json:"message,omitempty"`
	CallID    string                 `json:"call_id,omitempty"`
	Reason    string                 `json:"reason,omitempty"`
	Metadata  map[string]interface{} `json:"metadata,omitempty"`
}

// AskResult represents the user's response to an ask request
type AskResult struct {
	ID           string                 `json:"id"`
	Approved     bool                   `json:"approved,omitempty"`
	Response     string                 `json:"response,omitempty"`
	Selection    map[string]interface{} `json:"selection,omitempty"`
	Remember     bool                   `json:"remember,omitempty"`
	Reason       string                 `json:"reason,omitempty"`
	UpdatedInput map[string]interface{} `json:"updated_input,omitempty"`
}

// CompletionResult contains the final result information
type CompletionResult struct {
	Success     bool
	DurationMS  int64
	SessionID   string
	Error       string
	ExtraFields map[string]any
}

// ExecutionOptions controls agent execution
type ExecutionOptions struct {
	ProjectPath  string
	OutputFormat OutputFormat
	Timeout      time.Duration
	Env          []string
	// SessionID is the session ID to use or resume
	// If Resume is true, --resume <session_id> is used to continue an existing session
	// If Resume is false, --session-id <session_id> is used to create a new session with specific ID
	SessionID string
	// Resume indicates whether to resume an existing session (true) or create a new one (false)
	Resume bool
	// ChatID is the chat ID for permission requests (used by mock agent)
	ChatID string
	// Platform is the platform for permission requests (used by mock agent)
	Platform string
	// BotUUID is the bot UUID for permission callbacks
	BotUUID string

	// Model selection (per-execution override)
	Model         string
	FallbackModel string

	// Execution control
	MaxTurns int

	// Tool filtering (per-execution override)
	AllowedTools    []string
	DisallowedTools []string

	// MCP servers (per-execution override)
	MCPServers      map[string]interface{}
	StrictMcpConfig bool

	// System prompts (per-execution override)
	CustomSystemPrompt string
	AppendSystemPrompt string

	// Permission mode (per-execution override)
	PermissionMode string

	// Settings path (per-execution override)
	SettingsPath string

	// PermissionPromptTool specifies the tool for permission prompts (e.g., "stdio")
	// When set to "stdio", permission requests are sent via stdin/stdout for callback handling
	PermissionPromptTool string

	// Store, if set, receives session lifecycle events driven by the runner.
	// When non-nil and SessionID is non-empty the runner calls:
	//   SetRunning  — after the process starts successfully
	//   SetFailed   — if the process fails to start or Wait returns an error
	//   SetCompleted — if Wait returns without error
	Store agentsession.Store
}

// Result represents the result of an agent execution
type Result struct {
	Output   string // Agent output (text mode)
	ExitCode int    // Process exit code
	Error    string // Error message if failed
	Duration time.Duration
	Format   OutputFormat           // Output format used
	Events   []Event                // Stream events (stream-json mode)
	Metadata map[string]interface{} // Additional metadata
}

// TextOutput returns the full text output from the result
func (r *Result) TextOutput() string {
	if r == nil {
		return ""
	}

	switch r.Format {
	case OutputFormatStreamJSON:
		var output strings.Builder
		for _, event := range r.Events {
			// Handle SDK stream types
			if event.Type == "assistant" {
				// Assistant events contain the message
				if message, ok := event.Data["message"].(string); ok {
					output.WriteString(message)
				}
			} else if event.Type == "text_delta" {
				// Legacy: text_delta events
				if delta, ok := event.Data["delta"].(string); ok {
					output.WriteString(delta)
				}
			} else if event.Type == "text" {
				// Legacy: text events
				if text, ok := event.Data["text"].(string); ok {
					output.WriteString(text)
				}
			}
		}
		return output.String()
	case OutputFormatText:
		return r.Output
	default:
		return r.Output
	}
}

// GetStatus extracts the final status from events
func (r *Result) GetStatus() string {
	if r == nil || r.Format != OutputFormatStreamJSON {
		return "unknown"
	}

	for i := len(r.Events) - 1; i >= 0; i-- {
		if r.Events[i].Type == "status" {
			if status, ok := r.Events[i].Data["status"].(string); ok {
				return status
			}
		}
	}
	return "unknown"
}

// IsSuccess returns true if the execution was successful
func (r *Result) IsSuccess() bool {
	return r != nil && r.ExitCode == 0 && r.Error == ""
}

// GetMessagesByType returns all events of a specific type
func (r *Result) GetMessagesByType(messageType string) []Event {
	if r == nil {
		return nil
	}

	var result []Event
	for _, event := range r.Events {
		if event.Type == messageType {
			result = append(result, event)
		}
	}
	return result
}

// GetMessageChain returns all events in order, excluding result/system events
func (r *Result) GetMessageChain() []Event {
	if r == nil {
		return nil
	}

	var result []Event
	for _, event := range r.Events {
		// Skip system and result types for message chain
		if event.Type != "system" && event.Type != "result" && !strings.HasPrefix(event.Type, "control_") {
			result = append(result, event)
		}
	}
	return result
}

// GetAssistantMessages returns all assistant message events
func (r *Result) GetAssistantMessages() []Event {
	return r.GetMessagesByType("assistant")
}

// GetToolUseMessages returns all tool_use message events
func (r *Result) GetToolUseMessages() []Event {
	return r.GetMessagesByType("tool_use")
}

// GetToolResultMessages returns all tool_result message events
func (r *Result) GetToolResultMessages() []Event {
	return r.GetMessagesByType("tool_result")
}

// GetUserMessages returns all user message events
func (r *Result) GetUserMessages() []Event {
	return r.GetMessagesByType("user")
}

// GetSessionID extracts the session ID from metadata or events
func (r *Result) GetSessionID() string {
	if r == nil {
		return ""
	}

	// Check metadata first
	if sessionID, ok := r.Metadata["session_id"].(string); ok {
		return sessionID
	}

	// Look in events for session_id
	for _, event := range r.Events {
		if sessionID, ok := event.Data["session_id"].(string); ok && sessionID != "" {
			return sessionID
		}
	}

	return ""
}

// GetCostUSD extracts the total cost from result events if available
func (r *Result) GetCostUSD() float64 {
	if r == nil {
		return 0
	}

	for _, event := range r.Events {
		if event.Type == "result" {
			if cost, ok := event.Data["total_cost_usd"].(float64); ok {
				return cost
			}
		}
	}

	return 0
}

// PermissionRequest represents a permission request from an agent
type PermissionRequest struct {
	RequestID string                 `json:"request_id"`
	AgentType AgentType              `json:"agent_type"`
	ToolName  string                 `json:"tool_name"`
	Input     map[string]interface{} `json:"input"`
	Reason    string                 `json:"reason,omitempty"`
	Timestamp time.Time              `json:"timestamp"`
	SessionID string                 `json:"session_id,omitempty"`
	BotUUID   string                 `json:"bot_uuid,omitempty"` // Bot UUID for routing permission requests
	ChatID    string                 `json:"chat_id,omitempty"`  // Chat ID for routing
	Platform  string                 `json:"platform,omitempty"` // Platform for routing
}

// PermissionResponse represents the response to a permission request
type PermissionResponse struct {
	RequestID string    `json:"request_id"`
	Approved  bool      `json:"approved"`
	Reason    string    `json:"reason,omitempty"`
	Timestamp time.Time `json:"timestamp"`
}

// PermissionResult represents the result of a permission check
type PermissionResult struct {
	Approved     bool                   `json:"approved"`
	Reason       string                 `json:"reason,omitempty"`
	UpdatedInput map[string]interface{} `json:"updated_input,omitempty"`
	Remember     bool                   `json:"remember,omitempty"`
}

// PermissionConfig holds permission handler configuration
type PermissionConfig struct {
	DefaultMode       PermissionMode `json:"default_mode"`
	Timeout           time.Duration  `json:"timeout"`
	EnableWhitelist   bool           `json:"enable_whitelist"`
	Whitelist         []string       `json:"whitelist"`
	Blacklist         []string       `json:"blacklist"`
	RememberDecisions bool           `json:"remember_decisions"`
	DecisionDuration  time.Duration  `json:"decision_duration"`
}

// Event represents a generic agent event.
// Alias of common.Event — the two types are identical and interchangeable.
type Event = common.Event

// Agent is the interface for all agent types.
//
// Execute returns an [ExecutionHandle]; the caller iterates handle.Events()
// to consume the totally-ordered event stream, calls handle.Respond(...) to
// answer Approval/Ask requests, and calls handle.Wait() to obtain the
// aggregated [Result]. See the ExecutionHandle docs for lifecycle details.
type Agent interface {
	Execute(ctx context.Context, prompt string, opts ExecutionOptions) (ExecutionHandle, error)
	IsAvailable() bool
	Type() AgentType
	SetDefaultFormat(format OutputFormat)
	GetDefaultFormat() OutputFormat
}
