//go:build !omit_discord

package channels

import (
	"context"
	"fmt"
	"github.com/YspCoder/clawgo/pkg/bus"
	"github.com/YspCoder/clawgo/pkg/config"
	"github.com/YspCoder/clawgo/pkg/logger"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"github.com/bwmarrin/discordgo"
)

type DiscordChannel struct {
	*BaseChannel
	session *discordgo.Session
	config  config.DiscordConfig
}

const discordCompiled = true

func NewDiscordChannel(cfg config.DiscordConfig, bus *bus.MessageBus) (*DiscordChannel, error) {
	session, err := discordgo.New("Bot " + cfg.Token)
	if err != nil {
		return nil, fmt.Errorf("failed to create discord session: %w", err)
	}

	base := NewBaseChannel("discord", cfg, bus, cfg.AllowFrom)

	return &DiscordChannel{
		BaseChannel: base,
		session:     session,
		config:      cfg,
	}, nil
}

func (c *DiscordChannel) Start(ctx context.Context) error {
	logger.InfoC("discord", logger.C0069)

	c.session.AddHandler(c.handleMessage)

	if err := c.session.Open(); err != nil {
		return fmt.Errorf("failed to open discord session: %w", err)
	}

	c.setRunning(true)

	botUser, err := c.session.User("@me")
	if err != nil {
		return fmt.Errorf("failed to get bot user: %w", err)
	}
	logger.InfoCF("discord", logger.C0070, map[string]interface{}{
		"username": botUser.Username,
		"user_id":  botUser.ID,
	})

	return nil
}

func (c *DiscordChannel) Stop(ctx context.Context) error {
	logger.InfoC("discord", logger.C0071)
	c.setRunning(false)

	if err := c.session.Close(); err != nil {
		return fmt.Errorf("failed to close discord session: %w", err)
	}

	return nil
}

func (c *DiscordChannel) Send(ctx context.Context, msg bus.OutboundMessage) error {
	if !c.IsRunning() {
		return fmt.Errorf("discord bot not running")
	}

	channelID := msg.ChatID
	if channelID == "" {
		return fmt.Errorf("channel ID is empty")
	}

	message := msg.Content

	if _, err := c.session.ChannelMessageSend(channelID, message); err != nil {
		return fmt.Errorf("failed to send discord message: %w", err)
	}

	return nil
}

func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.MessageCreate) {
	if m == nil || m.Author == nil {
		return
	}

	if m.Author.ID == s.State.User.ID {
		return
	}

	senderID := m.Author.ID
	senderName := m.Author.Username
	if m.Author.Discriminator != "" && m.Author.Discriminator != "0" {
		senderName += "#" + m.Author.Discriminator
	}

	content := m.Content
	mediaPaths := []string{}

	for _, attachment := range m.Attachments {
		isAudio := isAudioFile(attachment.Filename, attachment.ContentType)

		if isAudio {
			localPath := c.downloadAttachment(attachment.URL, attachment.Filename)
			if localPath != "" {
				mediaPaths = append(mediaPaths, localPath)

				transcribedText := fmt.Sprintf("[audio: %s]", localPath)

				if content != "" {
					content += "\n"
				}
				content += transcribedText
			} else {
				mediaPaths = append(mediaPaths, attachment.URL)
				if content != "" {
					content += "\n"
				}
				content += fmt.Sprintf("[attachment: %s]", attachment.URL)
			}
		} else {
			mediaPaths = append(mediaPaths, attachment.URL)
			if content != "" {
				content += "\n"
			}
			content += fmt.Sprintf("[attachment: %s]", attachment.URL)
		}
	}

	if content == "" && len(mediaPaths) == 0 {
		return
	}

	if content == "" {
		content = "[media only]"
	}

	logger.DebugCF("discord", logger.C0072, map[string]interface{}{
		"sender_name":        senderName,
		logger.FieldSenderID: senderID,
		logger.FieldPreview:  truncateString(content, 50),
	})

	metadata := map[string]string{
		"message_id":   m.ID,
		"user_id":      senderID,
		"username":     m.Author.Username,
		"display_name": senderName,
		"guild_id":     m.GuildID,
		"channel_id":   m.ChannelID,
		"is_dm":        fmt.Sprintf("%t", m.GuildID == ""),
	}

	c.HandleMessage(senderID, m.ChannelID, content, mediaPaths, metadata)
}

func isAudioFile(filename, contentType string) bool {
	audioExtensions := []string{".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac", ".wma"}
	audioTypes := []string{"audio/", "application/ogg", "application/x-ogg"}

	for _, ext := range audioExtensions {
		if strings.HasSuffix(strings.ToLower(filename), ext) {
			return true
		}
	}

	for _, audioType := range audioTypes {
		if strings.HasPrefix(strings.ToLower(contentType), audioType) {
			return true
		}
	}

	return false
}

func (c *DiscordChannel) downloadAttachment(url, filename string) string {
	mediaDir := filepath.Join(os.TempDir(), "clawgo_media")
	if err := os.MkdirAll(mediaDir, 0755); err != nil {
		logger.WarnCF("discord", logger.C0073, map[string]interface{}{
			logger.FieldError: err.Error(),
		})
		return ""
	}

	localPath := filepath.Join(mediaDir, filename)

	resp, err := http.Get(url)
	if err != nil {
		logger.WarnCF("discord", logger.C0074, map[string]interface{}{
			logger.FieldError: err.Error(),
		})
		return ""
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		logger.WarnCF("discord", logger.C0075, map[string]interface{}{
			"status_code": resp.StatusCode,
		})
		return ""
	}

	out, err := os.Create(localPath)
	if err != nil {
		logger.WarnCF("discord", logger.C0076, map[string]interface{}{
			logger.FieldError: err.Error(),
		})
		return ""
	}
	defer out.Close()

	_, err = io.Copy(out, resp.Body)
	if err != nil {
		logger.WarnCF("discord", logger.C0077, map[string]interface{}{
			logger.FieldError: err.Error(),
		})
		return ""
	}

	logger.DebugCF("discord", logger.C0078, map[string]interface{}{
		"path": localPath,
	})
	return localPath
}
