package mailstore

import (
	"strings"

	"mailgoserver/internal/db"
)

// FilterAction is the outcome of evaluating a mailbox's filter rules against one
// incoming message.
type FilterAction struct {
	Folder   string // non-empty: store here instead of INBOX
	MarkRead bool
	Drop     bool // don't store at all
	// ForwardTo: non-empty means also relay a copy of this message out to this
	// address — see smtpserver's deliverLocally, which actually sends it (this
	// package has no outbound SMTP capability of its own). KeepCopy controls whether
	// the recipient's own local copy is still stored too.
	ForwardTo string
	KeepCopy  bool
}

// ApplyRules evaluates a mailbox's filter rules in priority order and returns the
// first match's action (zero value if none match, meaning "store in INBOX, unread").
// headers should have "from"/"to"/"subject"/"body"/"has_attachment"/"recipient_type"
// keys — rules run at delivery time, before the message is encrypted and stored, so
// real header/body values are available, not just the plaintext cache columns used
// for fast IMAP listing. has_attachment is "yes"/"no" and recipient_type is
// "to"/"cc"/"bcc", both computed by the caller rather than being real headers.
func (s *Store) ApplyRules(mailboxID int64, headers map[string]string) (FilterAction, error) {
	rules, err := s.DB.ListRulesForMailbox(mailboxID)
	if err != nil {
		return FilterAction{}, err
	}
	for _, r := range rules {
		if !r.IsActive {
			continue
		}
		if !ruleMatches(r, headers) {
			continue
		}
		switch r.Action {
		case "move_to_folder":
			return FilterAction{Folder: r.ActionValue}, nil
		case "mark_as_spam":
			// Reuses the same Junk folder score-based quarantine already delivers
			// into (see smtpserver/session.go) — from the mailbox owner's
			// perspective it's the same "goes to Junk" outcome either way.
			return FilterAction{Folder: "Junk"}, nil
		case "delete":
			return FilterAction{Drop: true}, nil
		case "mark_read":
			return FilterAction{MarkRead: true}, nil
		case "forward":
			return FilterAction{ForwardTo: r.ActionValue, KeepCopy: r.ActionOptions().KeepCopy}, nil
		}
	}
	return FilterAction{}, nil
}

// ruleMatches combines a rule's conditions per its match type: "all" requires every
// condition to match (AND), "any" requires at least one (OR).
func ruleMatches(r db.MailboxFilterRule, headers map[string]string) bool {
	conditions, matchType := r.Conditions()
	if matchType == "any" {
		for _, c := range conditions {
			if matchCondition(c.Op, headers[c.Field], c.Value) {
				return true
			}
		}
		return false
	}
	for _, c := range conditions {
		if !matchCondition(c.Op, headers[c.Field], c.Value) {
			return false
		}
	}
	return true
}

// matchCondition compares a header's actual value against a condition's target.
// target may hold several "\n"-joined values (the rule builder's chip input, e.g.
// multiple From addresses or multiple subject keywords) — matches if any one does,
// mirroring how a real mail client's "is any of" condition works.
func matchCondition(op, value, target string) bool {
	value = strings.ToLower(value)
	for _, t := range strings.Split(target, "\n") {
		t = strings.ToLower(strings.TrimSpace(t))
		if t == "" {
			continue
		}
		switch op {
		case "contains":
			if strings.Contains(value, t) {
				return true
			}
		case "equals":
			if value == t {
				return true
			}
		case "starts_with":
			if strings.HasPrefix(value, t) {
				return true
			}
		}
	}
	return false
}
