package worker

import (
	"context"

	"github.com/rs/zerolog/log"
	"github.com/warmbly/warmbly/internal/app/worker/wmail"
	"github.com/warmbly/warmbly/internal/client/smtpimap/imap"
	"github.com/warmbly/warmbly/internal/models"
)

// HandleWarmupAction executes recipient-side warmup actions on the mailbox.
//
// The worker just runs whatever actions it receives, immediately. The
// recipient-side "dwell" and the immediate-vs-delayed split are now owned by the
// CONSUMER's durable schedule (internal/app/consumer/warmup_engagement_poller):
// the consumer publishes the immediate leg (folder + spam-rescue) right away and
// the delayed leg (read / important / star) when its fire_at passes, each with
// DelaySeconds=0. That makes the dwell survive a worker restart, which the old
// in-process time.AfterFunc here could not.
func (w *WorkerService) HandleWarmupAction(ctx context.Context, action models.WarmupEmailAction) error {
	log.Info().
		Str("email_id", action.EmailID.String()).
		Str("gmail_id", action.GmailID).
		Uint32("uid", action.UID).
		Uint32("mailbox_uid_validity", action.MailboxUIDValidity).
		Strs("actions", action.Actions).
		Msg("Processing warmup email action")

	if len(action.Actions) == 0 {
		return nil
	}

	w.mailManager.RLock()
	mail, exists := w.mailManager.Emails[action.EmailID]
	w.mailManager.RUnlock()
	if !exists {
		log.Warn().Str("email_id", action.EmailID.String()).Msg("Email account not found for warmup action")
		return nil
	}

	switch {
	case mail.GoogleData != nil && mail.GoogleData.Client != nil:
		w.runGoogleWarmupActions(ctx, mail, action)
	case mail.GraphData != nil && mail.GraphData.Client != nil:
		w.runGraphWarmupActions(ctx, mail, action)
	case mail.SmtpImapData != nil && mail.SmtpImapData.ImapClient != nil:
		w.runImapWarmupActions(ctx, mail, action)
	default:
		log.Warn().
			Str("email_id", action.EmailID.String()).
			Msg("No mail client available for warmup actions; skipping")
	}
	return nil
}

func (w *WorkerService) runGoogleWarmupActions(ctx context.Context, mail *wmail.WMail, action models.WarmupEmailAction) {
	for _, act := range action.Actions {
		switch act {
		case "move_to_warmbly":
			if err := mail.GoogleData.Client.ApplyLabel(ctx, action.GmailID, imap.WarmupFolderName); err != nil {
				log.Error().Err(err).Str("gmail_id", action.GmailID).Msg("Failed to apply Warmbly label")
			}
		case "mark_read":
			if err := mail.GoogleData.Client.MarkAsRead(ctx, action.GmailID); err != nil {
				log.Error().Err(err).Str("gmail_id", action.GmailID).Msg("Failed to mark as read")
			}
		case "remove_from_spam":
			if err := mail.GoogleData.Client.RemoveFromSpam(ctx, action.GmailID); err != nil {
				log.Error().Err(err).Str("gmail_id", action.GmailID).Msg("Failed to remove from spam")
			}
		case "mark_important":
			if err := mail.GoogleData.Client.MarkImportant(ctx, action.GmailID); err != nil {
				log.Error().Err(err).Str("gmail_id", action.GmailID).Msg("Failed to mark important")
			}
		case "star":
			if err := mail.GoogleData.Client.AddStar(ctx, action.GmailID); err != nil {
				log.Error().Err(err).Str("gmail_id", action.GmailID).Msg("Failed to star warmup message")
			}
		default:
			log.Warn().Str("action", act).Msg("Unknown warmup action")
		}
	}
}

// runGraphWarmupActions applies recipient-side warmup engagement to a Microsoft
// Graph mailbox. action.GmailID carries the Graph message id (the provider id
// field is provider-agnostic). Star maps to the follow-up flag, the closest
// Outlook equivalent of a Gmail star.
func (w *WorkerService) runGraphWarmupActions(ctx context.Context, mail *wmail.WMail, action models.WarmupEmailAction) {
	client := mail.GraphData.Client

	// A Graph message id changes whenever the message is moved (copy+delete), so
	// resolve the live id from the immutable RFC Message-ID before acting. This
	// leg may run after an earlier leg already moved the message to Warmbly.
	msgID := action.GmailID
	if action.RFCMessageID != "" {
		if resolved, err := client.ResolveMessageID(ctx, action.RFCMessageID); err == nil && resolved != "" {
			msgID = resolved
		}
	}

	for _, act := range action.Actions {
		switch act {
		case "move_to_warmbly":
			newID, err := client.MoveToFolder(ctx, msgID, imap.WarmupFolderName)
			if err != nil {
				log.Error().Err(err).Str("graph_id", msgID).Msg("Failed to move to Warmbly folder (Graph)")
				continue
			}
			if newID != "" {
				msgID = newID // subsequent actions target the moved copy
			}
		case "mark_read":
			if err := client.MarkAsRead(ctx, msgID); err != nil {
				log.Error().Err(err).Str("graph_id", msgID).Msg("Failed to mark as read (Graph)")
			}
		case "remove_from_spam":
			newID, err := client.RemoveFromSpam(ctx, msgID)
			if err != nil {
				log.Error().Err(err).Str("graph_id", msgID).Msg("Failed to rescue from junk (Graph)")
				continue
			}
			if newID != "" {
				msgID = newID
			}
		case "mark_important":
			if err := client.MarkImportant(ctx, msgID); err != nil {
				log.Error().Err(err).Str("graph_id", msgID).Msg("Failed to mark important (Graph)")
			}
		case "star":
			if err := client.AddFlag(ctx, msgID); err != nil {
				log.Error().Err(err).Str("graph_id", msgID).Msg("Failed to flag warmup message (Graph)")
			}
		default:
			log.Warn().Str("action", act).Msg("Unknown warmup action")
		}
	}
}

func (w *WorkerService) runImapWarmupActions(ctx context.Context, mail *wmail.WMail, action models.WarmupEmailAction) {
	sourceBox := lookupMailboxByUIDValidity(mail.SmtpImapData.Mailboxes, action.MailboxUIDValidity)
	if sourceBox == nil {
		log.Warn().
			Uint32("uid_validity", action.MailboxUIDValidity).
			Str("email_id", action.EmailID.String()).
			Msg("Source mailbox for warmup action not found; skipping")
		return
	}

	inboxBox := lookupInbox(mail.SmtpImapData.Mailboxes)
	inboxName := "INBOX"
	if inboxBox != nil {
		inboxName = inboxBox.Name
	}

	imapClient := mail.SmtpImapData.ImapClient
	uid := action.UID

	for _, act := range action.Actions {
		switch act {
		case "move_to_warmbly":
			if err := imapClient.MoveToFolder(ctx, sourceBox.Name, imap.WarmupFolderName, uid); err != nil {
				log.Error().Err(err).Uint32("uid", uid).Msg("Failed to move to Warmbly folder")
			}
		case "mark_read":
			if err := imapClient.MarkAsRead(ctx, sourceBox.Name, uid); err != nil {
				log.Error().Err(err).Uint32("uid", uid).Msg("Failed to mark as read (IMAP)")
			}
		case "remove_from_spam":
			if !imap.IsSpamMailbox(sourceBox.Name, sourceBox.Attrs) {
				continue
			}
			if err := imapClient.RemoveFromSpam(ctx, sourceBox.Name, inboxName, uid); err != nil {
				log.Error().Err(err).Uint32("uid", uid).Msg("Failed to remove from spam (IMAP)")
			}
		case "mark_important":
			if err := imapClient.MarkImportant(ctx, sourceBox.Name, uid); err != nil {
				log.Error().Err(err).Uint32("uid", uid).Msg("Failed to mark important (IMAP)")
			}
		case "star":
			// No-op on IMAP: \Flagged is already set by mark_important, so
			// starring here would just re-flag the same message. Star is a
			// Gmail-only distinct signal.
			continue
		default:
			log.Warn().Str("action", act).Msg("Unknown warmup action")
		}
	}
}

func lookupMailboxByUIDValidity(boxes []*models.Mailbox, uidValidity uint32) *models.Mailbox {
	for _, b := range boxes {
		if b != nil && b.UIDValidity == uidValidity {
			return b
		}
	}
	return nil
}

func lookupInbox(boxes []*models.Mailbox) *models.Mailbox {
	for _, b := range boxes {
		if b != nil && imap.IsInboxMailbox(b.Name, b.Attrs) {
			return b
		}
	}
	return nil
}
