package infra

import (
	"bytes"
	"context"
	"fmt"

	"github.com/microcosm-cc/bluemonday"
	"github.com/yuin/goldmark"
	"maunium.net/go/mautrix"
	"maunium.net/go/mautrix/event"
	"maunium.net/go/mautrix/id"
)

// matrixMarkdownToHTML convierte Markdown a HTML sanitizado con goldmark + bluemonday.
// El HTML resultante es seguro para incluir en formatted_body de un evento Matrix.
// Allowlist: bluemonday UGCPolicy + <details>, <summary>, <code>, <pre>.
func matrixMarkdownToHTML(markdown string) (string, error) {
	var buf bytes.Buffer
	if err := goldmark.Convert([]byte(markdown), &buf); err != nil {
		return "", fmt.Errorf("matrix_message_send: goldmark convert: %w", err)
	}
	p := bluemonday.UGCPolicy()
	p.AllowElements("details", "summary", "code", "pre")
	sanitized := p.SanitizeBytes(buf.Bytes())
	return string(sanitized), nil
}

// matrixSendEvent es el helper interno que llama a client.SendMessageEvent
// y devuelve el id.EventID asignado por Synapse.
func matrixSendEvent(ctx context.Context, client *mautrix.Client, roomID id.RoomID, eventType event.Type, content interface{}) (id.EventID, error) {
	resp, err := client.SendMessageEvent(ctx, roomID, eventType, content)
	if err != nil {
		return "", err
	}
	return resp.EventID, nil
}

// MatrixSendText envía un mensaje de texto plano (m.text) al room indicado.
// Si el room tiene E2EE activo y client.Crypto != nil, mautrix cifra automáticamente.
func MatrixSendText(ctx context.Context, client *mautrix.Client, roomID id.RoomID, body string) (id.EventID, error) {
	if client == nil {
		return "", fmt.Errorf("matrix_message_send: client no puede ser nil")
	}
	content := &event.MessageEventContent{
		MsgType: event.MsgText,
		Body:    body,
	}
	return matrixSendEvent(ctx, client, roomID, event.EventMessage, content)
}

// MatrixSendMarkdown convierte markdown a HTML con goldmark, lo sanitiza con bluemonday
// (UGCPolicy + <details>, <summary>, <code>, <pre>) y envía con format=org.matrix.custom.html.
// El campo Body contiene el markdown original como fallback para clientes sin HTML.
func MatrixSendMarkdown(ctx context.Context, client *mautrix.Client, roomID id.RoomID, markdown string) (id.EventID, error) {
	if client == nil {
		return "", fmt.Errorf("matrix_message_send: client no puede ser nil")
	}
	htmlBody, err := matrixMarkdownToHTML(markdown)
	if err != nil {
		return "", fmt.Errorf("matrix_message_send.MatrixSendMarkdown: %w", err)
	}
	content := &event.MessageEventContent{
		MsgType:       event.MsgText,
		Body:          markdown,
		Format:        event.FormatHTML,
		FormattedBody: htmlBody,
	}
	return matrixSendEvent(ctx, client, roomID, event.EventMessage, content)
}

// MatrixSendReply envía un mensaje con m.relates_to.m.in_reply_to apuntando a replyTo.
// El body es el texto de la respuesta. En v0.1.0 el caller construye la cita si la necesita.
// El cifrado E2EE es automático si client.Crypto está configurado.
func MatrixSendReply(ctx context.Context, client *mautrix.Client, roomID id.RoomID, replyTo id.EventID, body string) (id.EventID, error) {
	if client == nil {
		return "", fmt.Errorf("matrix_message_send: client no puede ser nil")
	}
	content := &event.MessageEventContent{
		MsgType:   event.MsgText,
		Body:      body,
		RelatesTo: (&event.RelatesTo{}).SetReplyTo(replyTo),
	}
	return matrixSendEvent(ctx, client, roomID, event.EventMessage, content)
}

// MatrixEditMessage envía un replacement event (m.replace) compatible con Element y la spec Matrix.
// NewContent contiene el texto nuevo; Body es el fallback "* newBody" para clientes sin soporte de edición.
// eventID es el evento original a reemplazar.
func MatrixEditMessage(ctx context.Context, client *mautrix.Client, roomID id.RoomID, eventID id.EventID, newBody string) (id.EventID, error) {
	if client == nil {
		return "", fmt.Errorf("matrix_message_send: client no puede ser nil")
	}
	content := &event.MessageEventContent{
		MsgType: event.MsgText,
		Body:    "* " + newBody,
		NewContent: &event.MessageEventContent{
			MsgType: event.MsgText,
			Body:    newBody,
		},
		RelatesTo: (&event.RelatesTo{}).SetReplace(eventID),
	}
	return matrixSendEvent(ctx, client, roomID, event.EventMessage, content)
}

// MatrixSendReaction envía un evento m.reaction con m.relates_to.rel_type=m.annotation.
// key debe ser el emoji unicode raw (ej. "👍"), no shortcode (:thumbsup:).
// Las reactions no se cifran aunque el room sea E2EE (comportamiento de mautrix-go).
func MatrixSendReaction(ctx context.Context, client *mautrix.Client, roomID id.RoomID, targetEventID id.EventID, key string) (id.EventID, error) {
	if client == nil {
		return "", fmt.Errorf("matrix_message_send: client no puede ser nil")
	}
	content := &event.ReactionEventContent{
		RelatesTo: event.RelatesTo{
			Type:    event.RelAnnotation,
			EventID: targetEventID,
			Key:     key,
		},
	}
	return matrixSendEvent(ctx, client, roomID, event.EventReaction, content)
}
