package message

import (
	"fmt"
	"os"
	"strings"
	"sync"

	"github.com/fatih/color"
	"github.com/gin-gonic/gin"
	jsoniter "github.com/json-iterator/go"
	"github.com/yaoapp/gou/helper"
	"github.com/yaoapp/kun/exception"
	"github.com/yaoapp/kun/log"
	"github.com/yaoapp/kun/maps"
	"github.com/yaoapp/yao/attachment"
	"github.com/yaoapp/yao/openai"
)

var locker = sync.Mutex{}

// Message the message
type Message struct {
	ID              string                  `json:"id,omitempty"`               // id for the message
	ToolID          string                  `json:"tool_id,omitempty"`          // tool_id for the message
	Text            string                  `json:"text,omitempty"`             // text content
	Type            string                  `json:"type,omitempty"`             // error, text, plan, table, form, page, file, video, audio, image, markdown, json ...
	Props           map[string]interface{}  `json:"props,omitempty"`            // props for the types
	IsDone          bool                    `json:"done,omitempty"`             // Mark as a done message from neo
	IsNew           bool                    `json:"new,omitempty"`              // Mark as a new message from neo
	IsDelta         bool                    `json:"delta,omitempty"`            // Mark as a delta message from neo
	Actions         []Action                `json:"actions,omitempty"`          // Conversation Actions for frontend
	Attachments     []attachment.Attachment `json:"attachments,omitempty"`      // File attachments
	Role            string                  `json:"role,omitempty"`             // user, assistant, system ...
	Name            string                  `json:"name,omitempty"`             // name for the message
	AssistantID     string                  `json:"assistant_id,omitempty"`     // assistant_id (for assistant role = assistant )
	AssistantName   string                  `json:"assistant_name,omitempty"`   // assistant_name (for assistant role = assistant )
	AssistantAvatar string                  `json:"assistant_avatar,omitempty"` // assistant_avatar (for assistant role = assistant )
	Mentions        []Mention               `json:"menions,omitempty"`          // Mentions for the message ( for user  role = user )
	Data            map[string]interface{}  `json:"-"`                          // data for the message
	Pending         bool                    `json:"-"`                          // pending for the message
	Hidden          bool                    `json:"hidden,omitempty"`           // hidden for the message (not show in the UI and history)
	Retry           bool                    `json:"retry,omitempty"`            // retry for the message
	Silent          bool                    `json:"silent,omitempty"`           // silent for the message (not show in the UI and history)
	IsTool          bool                    `json:"-"`                          // is tool for the message for native tool_calls
	IsBeginTool     bool                    `json:"-"`                          // is new tool for the message for native tool_calls
	IsEndTool       bool                    `json:"-"`                          // is end tool for the message for native tool_calls
	Result          any                     `json:"result,omitempty"`           // result for the message
	Begin           int64                   `json:"begin,omitempty"`            // begin at for the message // timestamp
	End             int64                   `json:"end,omitempty"`              // end at for the message // timestamp
}

// Mention represents a mention
type Mention struct {
	ID     string `json:"assistant_id"`     // assistant_id
	Name   string `json:"name"`             // name
	Avatar string `json:"avatar,omitempty"` // avatar
}

// Action the action
type Action struct {
	Name    string      `json:"name,omitempty"`
	Type    string      `json:"type"`
	Payload interface{} `json:"payload,omitempty"`
}

// New create a new message
func New() *Message {
	return &Message{Actions: []Action{}, Props: map[string]interface{}{}}
}

// NewHistory create a new message from history
func NewHistory(history map[string]interface{}) ([]Message, error) {
	if history == nil {
		return []Message{}, nil
	}

	var copy map[string]interface{} = map[string]interface{}{}
	for key, value := range history {
		if key != "content" {
			copy[key] = value
		}
	}

	globalMessage := New().Map(copy)
	messages := []Message{}
	if content, ok := history["content"].(string); ok {
		if strings.HasPrefix(content, "{") && strings.HasSuffix(content, "}") {
			var msg Message = *globalMessage
			if err := jsoniter.UnmarshalFromString(content, &msg); err != nil {
				return nil, err
			}
			messages = append(messages, msg)
		} else if strings.HasPrefix(content, "[") && strings.HasSuffix(content, "]") {
			var msgs []Message
			if err := jsoniter.UnmarshalFromString(content, &msgs); err != nil {
				return nil, err
			}
			for _, msg := range msgs {
				msg.AssistantID = globalMessage.AssistantID
				msg.AssistantName = globalMessage.AssistantName
				msg.AssistantAvatar = globalMessage.AssistantAvatar
				msg.Role = globalMessage.Role
				msg.Name = globalMessage.Name
				msg.Mentions = globalMessage.Mentions
				messages = append(messages, msg)
			}
		} else {
			messages = append(messages, Message{Text: content})
		}
	}

	return messages, nil
}

// NewContent create a new message from content
func NewContent(content string) ([]Message, error) {
	messages := []Message{}
	if strings.HasPrefix(content, "{") && strings.HasSuffix(content, "}") {
		var msg Message
		if err := jsoniter.UnmarshalFromString(content, &msg); err != nil {
			return nil, err
		}
		messages = append(messages, msg)
	} else if strings.HasPrefix(content, "[") && strings.HasSuffix(content, "]") {
		var msgs []Message
		if err := jsoniter.UnmarshalFromString(content, &msgs); err != nil {
			return nil, err
		}
		for _, msg := range msgs {
			messages = append(messages, msg)
		}
	} else {
		messages = append(messages, Message{Text: content})
	}
	return messages, nil
}

// NewString create a new message from string
func NewString(content string, id ...string) (*Message, error) {
	if strings.HasPrefix(content, "{") && strings.HasSuffix(content, "}") {
		var msg Message
		if err := jsoniter.UnmarshalFromString(content, &msg); err != nil {
			return nil, err
		}
		return &msg, nil
	}
	if len(id) > 0 {
		return &Message{ID: id[0], Text: content}, nil
	}
	return &Message{Text: content}, nil
}

// NewStringError create a new message from string error
func NewStringError(content string) (*Message, error) {
	if strings.HasPrefix(content, "{") && strings.HasSuffix(content, "}") {
		var msg = New()
		var errorMessage openai.ErrorMessage
		if err := jsoniter.UnmarshalFromString(content, &errorMessage); err != nil {
			msg.Text = err.Error() + "\n" + content
			return msg, nil
		}
		msg.Type = "error"
		msg.Text = errorMessage.Error.Message
		return msg, nil
	}
	return &Message{Text: content}, nil
}

// NewMap create a new message from map
func NewMap(content map[string]interface{}) (*Message, error) {
	return New().Map(content), nil
}

// NewAny create a new message from any content
func NewAny(content interface{}) (*Message, error) {
	switch v := content.(type) {
	case string:
		return NewString(v)
	case map[string]interface{}:
		return NewMap(v)
	}
	return nil, fmt.Errorf("unknown content type: %T", content)
}

// NewOpenAI create a new message from OpenAI response
func NewOpenAI(data []byte, isThinking bool) *Message {

	// For debug environment, print the response data
	if os.Getenv("YAO_AGENT_PRINT_RESPONSE_DATA") == "true" {
		log.Trace("[Response Data] %s", string(data))
	}

	if data == nil || len(data) == 0 {
		return nil
	}

	msg := New()
	text := string(data)
	data = []byte(strings.TrimPrefix(text, "data: "))

	switch {
	case strings.Contains(text, `"object":"chat.completion.chunk"`): // Delta content
		var chunk openai.ChatCompletionChunk
		err := jsoniter.Unmarshal(data, &chunk)
		if err != nil {
			color.Red("JSON parse error: %s", err.Error())
			color.White(string(data))
			msg.Text = "JSON parse error\n" + string(data)
			msg.Type = "error"
			msg.IsDone = true
		}

		// Empty content, then it is a pending message
		if len(chunk.Choices) == 0 {
			msg.Pending = true
			return msg
		}

		// Tool calls
		if len(chunk.Choices[0].Delta.ToolCalls) > 0 || chunk.Choices[0].FinishReason == "tool_calls" {
			msg.Type = "tool_calls_native"
			text := ""
			if len(chunk.Choices[0].Delta.ToolCalls) > 0 {
				id := chunk.Choices[0].Delta.ToolCalls[0].ID
				function := chunk.Choices[0].Delta.ToolCalls[0].Function.Name
				arguments := chunk.Choices[0].Delta.ToolCalls[0].Function.Arguments
				text = arguments
				if id != "" {
					msg.IsBeginTool = true
					msg.IsNew = true // mark as a new message
					text = fmt.Sprintf(`{"id": "%s", "function": "%s", "arguments": %s`, id, function, arguments)
				}
			}

			if chunk.Choices[0].FinishReason == "tool_calls" {
				msg.IsEndTool = true
			}

			msg.Text = text
			return msg
		}

		// Text content
		if chunk.Choices[0].Delta.Content != "" {
			msg.Type = "text"
			msg.Text = chunk.Choices[0].Delta.Content
			msg.IsDone = chunk.Choices[0].FinishReason == "stop" // is done when the content is finished
			return msg
		}

		// Done messages
		if chunk.Choices[0].FinishReason == "stop" || chunk.Choices[0].FinishReason == "tool_calls" {
			msg.IsDone = true
			return msg
		}

		// Reasoning content
		if chunk.Choices[0].Delta.ReasoningContent != "" {
			msg.Type = "think"
			msg.Text = chunk.Choices[0].Delta.ReasoningContent
			return msg
		}
		// Content is empty and is thinking, then it is a thinking message pending
		if isThinking {
			msg.Type = "think"
			msg.Text = ""
			return msg
		}

		msg.Text = ""
		return msg

	case strings.Contains(text, `"usage":`): // usage content
		msg.IsDone = true
		break

	case strings.Contains(text, `[DONE]`):
		msg.IsDone = true
		return msg

	case len(data) > 2 && data[0] == '{' && data[len(data)-1] == '}': // JSON content (error)

		var error openai.Error
		var errorMessage openai.ErrorMessage
		if strings.Contains(string(data), `"error":`) {
			if err := jsoniter.Unmarshal(data, &errorMessage); err != nil {
				color.Red("JSON parse error: %s", err.Error())
				color.White(string(data))
				msg.Text = "JSON parse error\n" + string(data)
				msg.Type = "error"
				msg.IsDone = true
				return msg
			}
			error = errorMessage.Error
		} else {
			err := jsoniter.Unmarshal(data, &error)
			if err != nil {
				color.Red("JSON parse error: %s", err.Error())
				color.White(string(data))
				msg.Text = "JSON parse error\n" + string(data)
				msg.Type = "error"
				msg.IsDone = true
				return msg
			}
		}

		message := error.Message
		if message == "" {
			message = "Unknown error occurred\n" + string(data)
		}

		msg.Type = "error"
		msg.Text = message
		msg.IsDone = true
		return msg

	case !strings.Contains(text, `data: `): // unknown message or uncompleted message
		msg.Pending = true
		msg.Text = text
		return msg

	default: // unknown message
		str := strings.TrimPrefix(strings.Trim(string(data), "\""), "data: ")
		msg.Type = "error"
		msg.Text = str
		return msg
	}

	return msg
}

// String returns the string representation
func (m *Message) String() string {
	typ := m.Type
	if typ == "" {
		typ = "text"
	}

	switch typ {
	case "text", "think", "tool", "tool_calls_native":
		return m.Text

	case "error":
		return m.Text

	default:
		raw, _ := jsoniter.MarshalToString(map[string]interface{}{"type": m.Type, "props": m.Props})
		return raw
	}
}

// SetText set the text
func (m *Message) SetText(text string) *Message {
	m.Text = text
	if m.Data != nil {
		if replaced := helper.Bind(text, m.Data); replaced != nil {
			if replacedText, ok := replaced.(string); ok {
				m.Text = replacedText
			}
		}
	}
	return m
}

// SetProps set the props
func (m *Message) SetProps(props map[string]interface{}) *Message {
	m.Props = props
	return m
}

// Error set the error
func (m *Message) Error(message interface{}) *Message {
	m.Type = "error"
	switch v := message.(type) {
	case error:
		m.Text = v.Error()
	case string:
		m.Text = v
	default:
		m.Text = fmt.Sprintf("%v", message)
	}
	return m
}

// SetResult set the result
func (m *Message) SetResult(result any) *Message {
	m.Result = result
	m.Type = "result" // set the type to result
	return m
}

// SetContent set the content
func (m *Message) SetContent(content string) *Message {
	if strings.HasPrefix(content, "{") && strings.HasSuffix(content, "}") {
		var msg Message
		if err := jsoniter.UnmarshalFromString(content, &msg); err != nil {
			m.Text = err.Error() + "\n" + content
			return m
		}
		*m = msg
	} else {
		m.Text = content
		m.Type = "text"
	}
	return m
}

// AppendTo append the contents
func (m *Message) AppendTo(contents *Contents) *Message {

	// Set type
	if m.Type == "" {
		m.Type = "text"
	}

	switch m.Type {
	case "text", "think", "tool", "tool_calls_native":
		if m.Text != "" {
			if m.IsNew {
				contents.NewText([]byte(m.Text), Extra{ID: m.ID, Begin: m.Begin, End: m.End})
				return m
			}
			contents.AppendText([]byte(m.Text), Extra{ID: m.ID, Begin: m.Begin, End: m.End})
			return m
		}
		return m

	case "loading", "error", "action", "progress", "plan", "result": // Ignore progress, loading, plan and error messages
		return m

	default:
		if m.IsNew {
			contents.NewType(m.Type, m.Props)
			return m
		}
		contents.UpdateType(m.Type, m.Props)
		return m
	}

}

// Content get the content
func (m *Message) Content() string {
	content := map[string]interface{}{"text": m.Text}
	if m.Attachments != nil {
		content["attachments"] = m.Attachments
	}

	if m.Type != "" {
		content["type"] = m.Type
	}
	contentRaw, _ := jsoniter.MarshalToString(content)
	return contentRaw
}

// ToMap convert to map
func (m *Message) ToMap() map[string]interface{} {
	return map[string]interface{}{
		"content": m.Content(),
		"role":    m.Role,
		"name":    m.Name,
	}
}

// Map set from map
func (m *Message) Map(msg map[string]interface{}) *Message {
	if msg == nil {
		return m
	}

	// Content  {"text": "xxxx",  "attachments": ... }
	if content, ok := msg["content"].(string); ok {
		if strings.HasPrefix(content, "{") && strings.HasSuffix(content, "}") {
			var msg Message
			if err := jsoniter.UnmarshalFromString(content, &msg); err != nil {
				m.Text = err.Error() + "\n" + content
				return m
			}
			*m = msg
		} else {
			m.Text = content
			m.Type = "text"
		}
	}

	// attachments
	if attachments, has := msg["attachments"]; has {
		raw, _ := jsoniter.Marshal(attachments)
		m.Attachments = []attachment.Attachment{}
		if err := jsoniter.Unmarshal(raw, &m.Attachments); err != nil {
			color.Red("JSON parse error: %s", err.Error())
			color.White(string(raw))
		}
	}

	if role, ok := msg["role"].(string); ok {
		m.Role = role
	}

	if name, ok := msg["name"].(string); ok {
		m.Name = name
	}

	if text, ok := msg["text"].(string); ok {
		m.Text = text
	}
	if typ, ok := msg["type"].(string); ok {
		m.Type = typ
	}
	if done, ok := msg["done"].(bool); ok {
		m.IsDone = done
	}
	if props, ok := msg["props"].(map[string]interface{}); ok {
		m.Props = props
	}

	if isNew, ok := msg["new"].(bool); ok {
		m.IsNew = isNew
	}

	if isDelta, ok := msg["delta"].(bool); ok {
		m.IsDelta = isDelta
	}

	if assistantID, ok := msg["assistant_id"].(string); ok {
		m.AssistantID = assistantID

		// Set name
		if m.Role == "assistant" {
			m.Name = m.AssistantID
		}
	}

	if assistantName, ok := msg["assistant_name"].(string); ok {
		m.AssistantName = assistantName
	}

	if assistantAvatar, ok := msg["assistant_avatar"].(string); ok {
		m.AssistantAvatar = assistantAvatar
	}

	if actions, ok := msg["actions"].([]interface{}); ok {
		for _, action := range actions {
			if v, ok := action.(map[string]interface{}); ok {
				action := Action{}
				if name, ok := v["name"].(string); ok {
					action.Name = name
				}
				if t, ok := v["type"].(string); ok {
					action.Type = t
				}
				if payload, ok := v["payload"].(map[string]interface{}); ok {
					action.Payload = payload
				}
				m.Actions = append(m.Actions, action)
			}
		}
	}
	if data, ok := msg["data"].(map[string]interface{}); ok {
		m.Data = data
	}
	return m
}

// Done set the done flag
func (m *Message) Done() *Message {
	m.IsDone = true
	return m
}

// Assistant set the assistant
func (m *Message) Assistant(id string, name string, avatar string) *Message {
	m.AssistantID = id
	m.AssistantName = name
	m.AssistantAvatar = avatar
	return m
}

// Action add an action
func (m *Message) Action(name string, t string, payload interface{}, next string) *Message {
	if m.Data != nil {
		payload = helper.Bind(payload, m.Data)
	}
	m.Actions = append(m.Actions, Action{
		Name:    name,
		Type:    t,
		Payload: payload,
	})
	return m
}

// Bind replace with data
func (m *Message) Bind(data map[string]interface{}) *Message {
	if data == nil {
		return m
	}
	m.Data = maps.Of(data).Dot()
	return m
}

// Callback callback the message
func (m *Message) Callback(fn interface{}) *Message {
	if fn != nil {
		switch v := fn.(type) {
		case func(msg *Message):
			if v == nil {
				break
			}
			v(m)
			break

		case func():
			if v == nil {
				break
			}
			v()
			break

		default:
			fmt.Println("no match callback")
			break
		}
	}
	return m
}

// WriteError writes an error message to response writer
func (m *Message) WriteError(w gin.ResponseWriter, message string) {
	errMsg := strings.Trim(exception.New(message, 500).Message, "\"")
	data := []byte(fmt.Sprintf(`{"text":"%s","type":"error"`, errMsg))
	if m.IsDone {
		data = []byte(fmt.Sprintf(`{"text":"%s","type":"error","done":true`, errMsg))
	}
	data = append([]byte("data: "), data...)
	data = append(data, []byte("}\n\n")...)

	if _, err := w.Write(data); err != nil {
		color.Red("Write JSON Message Error: %s", message)
	}
	w.Flush()
}

// MarshalJSON implements json.Marshaler interface
func (m *Message) MarshalJSON() ([]byte, error) {
	type Alias Message
	return jsoniter.Marshal(&struct {
		*Alias
	}{
		Alias: (*Alias)(m),
	})
}

// UnmarshalJSON implements json.Unmarshaler interface
func (m *Message) UnmarshalJSON(data []byte) error {
	type Alias Message
	aux := &struct {
		*Alias
	}{
		Alias: (*Alias)(m),
	}
	if err := jsoniter.Unmarshal(data, &aux); err != nil {
		return err
	}
	return nil
}

// MarshalJSON implements json.Marshaler interface
func (a *Action) MarshalJSON() ([]byte, error) {
	type Alias Action
	return jsoniter.Marshal(&struct {
		*Alias
	}{
		Alias: (*Alias)(a),
	})
}

// UnmarshalJSON implements json.Unmarshaler interface
func (a *Action) UnmarshalJSON(data []byte) error {
	type Alias Action
	aux := &struct {
		*Alias
	}{
		Alias: (*Alias)(a),
	}
	if err := jsoniter.Unmarshal(data, &aux); err != nil {
		return err
	}
	return nil
}

// Write writes the message to response writer using the message queue
func (m *Message) Write(w gin.ResponseWriter) bool {
	return WriteMessageAsync(m, w)
}
