package bot

import (
	"context"
	"fmt"
	"regexp"
	"sync"

	"github.com/rs/zerolog"
	"gitlab.com/etke.cc/linkpearl"
	"maunium.net/go/mautrix/event"
	"maunium.net/go/mautrix/id"

	"gitlab.com/etke.cc/postmoogle/bot/config"
	"gitlab.com/etke.cc/postmoogle/bot/queue"
	"gitlab.com/etke.cc/postmoogle/utils"
)

// Mailboxes config
type MBXConfig struct {
	Reserved   []string
	Forwarded  []string
	Activation string
}

// Bot represents matrix bot
type Bot struct {
	prefix                  string
	mbxc                    MBXConfig
	domains                 []string
	allowedUsers            []*regexp.Regexp
	allowedAdmins           []*regexp.Regexp
	adminRooms              []id.RoomID
	ignoreBefore            int64 // mautrix 0.15.x migration
	commands                commandList
	rooms                   sync.Map
	proxies                 []string
	sendmail                func(string, string, string) error
	cfg                     *config.Manager
	log                     *zerolog.Logger
	lp                      *linkpearl.Linkpearl
	mu                      utils.Mutex
	q                       *queue.Queue
	handledMembershipEvents sync.Map
}

// New creates a new matrix bot
func New(
	q *queue.Queue,
	lp *linkpearl.Linkpearl,
	log *zerolog.Logger,
	cfg *config.Manager,
	proxies []string,
	prefix string,
	domains []string,
	admins []string,
	mbxc MBXConfig,
) (*Bot, error) {
	b := &Bot{
		domains:    domains,
		prefix:     prefix,
		rooms:      sync.Map{},
		adminRooms: []id.RoomID{},
		proxies:    proxies,
		mbxc:       mbxc,
		cfg:        cfg,
		log:        log,
		lp:         lp,
		mu:         utils.NewMutex(),
		q:          q,
	}
	users, err := b.initBotUsers()
	if err != nil {
		return nil, err
	}
	allowedUsers, uerr := parseMXIDpatterns(users, "")
	if uerr != nil {
		return nil, uerr
	}
	b.allowedUsers = allowedUsers

	allowedAdmins, aerr := parseMXIDpatterns(admins, "")
	if aerr != nil {
		return nil, aerr
	}
	b.allowedAdmins = allowedAdmins

	b.commands = b.initCommands()

	return b, nil
}

// Error message to the log and matrix room
func (b *Bot) Error(ctx context.Context, message string, args ...interface{}) {
	evt := eventFromContext(ctx)
	threadID := threadIDFromContext(ctx)
	if threadID == "" {
		threadID = linkpearl.EventParent(evt.ID, evt.Content.AsMessage())
	}

	err := fmt.Errorf(message, args...)
	b.log.Error().Err(err).Msg(err.Error())
	if evt == nil {
		return
	}

	var noThreads bool
	cfg, cerr := b.cfg.GetRoom(evt.RoomID)
	if cerr == nil {
		noThreads = cfg.NoThreads()
	}

	var relatesTo *event.RelatesTo
	if threadID != "" {
		relatesTo = linkpearl.RelatesTo(threadID, noThreads)
	}

	b.lp.SendNotice(evt.RoomID, "ERROR: "+err.Error(), relatesTo)
}

// Start performs matrix /sync
func (b *Bot) Start(statusMsg string) error {
	if err := b.migrateMautrix015(); err != nil {
		return err
	}

	if err := b.syncRooms(); err != nil {
		return err
	}

	b.initSync()
	b.log.Info().Msg("Postmoogle has been started")
	return b.lp.Start(statusMsg)
}

// Stop the bot
func (b *Bot) Stop() {
	err := b.lp.GetClient().SetPresence(event.PresenceOffline)
	if err != nil {
		b.log.Error().Err(err).Msg("cannot set presence = offline")
	}
	b.lp.GetClient().StopSync()
}
