package store

import (
	"errors"
	"os"
	"path/filepath"

	"github.com/mjl-/mox/config"
	"github.com/mjl-/mox/junk"
	"github.com/mjl-/mox/mlog"
	"github.com/mjl-/mox/mox-"
)

// ErrNoJunkFilter indicates user did not configure/enable a junk filter.
var ErrNoJunkFilter = errors.New("junkfilter: not configured")

// OpenJunkFilter returns an opened junk filter for the account.
// If the account does not have a junk filter enabled, ErrNotConfigured is returned.
// Do not forget to save the filter after modifying, and to always close the filter when done.
// An empty filter is initialized on first access of the filter.
func (a *Account) OpenJunkFilter(log *mlog.Log) (*junk.Filter, *config.JunkFilter, error) {
	conf, ok := mox.Conf.Account(a.Name)
	if !ok {
		return nil, nil, ErrAccountUnknown
	}
	jf := conf.JunkFilter
	if jf == nil {
		return nil, jf, ErrNoJunkFilter
	}

	basePath := mox.DataDirPath("accounts")
	dbPath := filepath.Join(basePath, a.Name, "junkfilter.db")
	bloomPath := filepath.Join(basePath, a.Name, "junkfilter.bloom")

	if _, xerr := os.Stat(dbPath); xerr != nil && os.IsNotExist(xerr) {
		f, err := junk.NewFilter(log, jf.Params, dbPath, bloomPath)
		return f, jf, err
	}
	f, err := junk.OpenFilter(log, jf.Params, dbPath, bloomPath, false)
	return f, jf, err
}

// Train new messages, if relevant given their flags.
func (a *Account) Train(log *mlog.Log, msgs []Message) error {
	return a.xtrain(log, msgs, false, true)
}

// Untrain removed messages, if relevant given their flags.
func (a *Account) Untrain(log *mlog.Log, msgs []Message) error {
	return a.xtrain(log, msgs, true, false)
}

// train or untrain messages, if relevant given their flags.
func (a *Account) xtrain(log *mlog.Log, msgs []Message, untrain, train bool) (rerr error) {
	if len(msgs) == 0 {
		return nil
	}

	var jf *junk.Filter

	for _, m := range msgs {
		if !m.Seen && !m.Junk {
			continue
		}
		// Lazy open the junk filter.
		if jf == nil {
			var err error
			jf, _, err = a.OpenJunkFilter(log)
			if err != nil && errors.Is(err, ErrNoJunkFilter) {
				// No junk filter configured. Nothing more to do.
				return nil
			}
			defer func() {
				if jf != nil {
					err := jf.Close()
					if rerr == nil {
						rerr = err
					}
				}
			}()
		}
		ham := !m.Junk
		err := xtrainMessage(log, a, jf, m, untrain, ham, train, ham)
		if err != nil {
			return err
		}
	}
	return nil
}

// Retrain message, if relevant given old flags and the new flags in m.
func (a *Account) Retrain(log *mlog.Log, jf *junk.Filter, old Flags, m Message) error {
	untrain := old.Seen || old.Junk
	train := m.Seen || m.Junk
	untrainHam := !old.Junk
	trainHam := !m.Junk

	if !untrain && !train || (untrain && train && trainHam == untrainHam) {
		return nil
	}

	return xtrainMessage(log, a, jf, m, untrain, untrainHam, train, trainHam)
}

func xtrainMessage(log *mlog.Log, a *Account, jf *junk.Filter, m Message, untrain, untrainHam, train, trainHam bool) error {
	log.Info("updating junk filter", mlog.Field("untrain", untrain), mlog.Field("untrainHam", untrainHam), mlog.Field("train", train), mlog.Field("trainHam", trainHam))

	mr := a.MessageReader(m)
	defer mr.Close()

	p, err := m.LoadPart(mr)
	if err != nil {
		log.Errorx("loading part for message", err)
		return nil
	}

	words, err := jf.ParseMessage(p)
	if err != nil {
		log.Errorx("parsing message for updating junk filter", err, mlog.Field("parse", ""))
		return nil
	}

	if untrain {
		err := jf.Untrain(untrainHam, words)
		if err != nil {
			return err
		}
	}
	if train {
		err := jf.Train(trainHam, words)
		if err != nil {
			return err
		}
	}
	return nil
}