package worker

import (
	"fmt"
	"log"
	"math"
	"sync"
	"time"

	"inboxer/src/internal/db"
	"inboxer/src/internal/imap"
	"inboxer/src/pkg/config"
)

// AIClassifier is the interface for AI email classification.
// The real implementation will be added in Phase 3.
type AIClassifier interface {
	// Classify returns the target folder name for an email.
	// Returns folder name and confidence score (0-100).
	Classify(email imap.EmailSummary) (folder string, confidence int, err error)
}

// FolderConfig holds the target folder names for classification
type FolderConfig struct {
	Important     string
	Ecommerce     string
	Notifications string
	Finance       string
	Social        string
	Other         string
	Spam          string
}

// foldersList returns the list of target folders
func (f FolderConfig) foldersList() []string {
	return []string{f.Important, f.Ecommerce, f.Notifications, f.Finance, f.Social, f.Other, f.Spam}
}

// Worker processes emails for all configured users
type Worker struct {
	db            *db.Database
	cfg           *config.Config
	folders       FolderConfig
	classifier    AIClassifier
	stopCh        chan struct{}
	processNow    chan struct{} // signal to trigger immediate processing
	stopped       bool
	mu            sync.Mutex
	wg            sync.WaitGroup
	userWorkers   sync.WaitGroup
}

// NewWorker creates a new background worker
func NewWorker(database *db.Database, cfg *config.Config, classifier AIClassifier) *Worker {
	return &Worker{
		db:         database,
		cfg:        cfg,
		folders: FolderConfig{
			Important:     cfg.Folders.Important,
			Ecommerce:     cfg.Folders.Ecommerce,
			Notifications: cfg.Folders.Notifications,
			Finance:       cfg.Folders.Finance,
			Social:        cfg.Folders.Social,
			Other:         cfg.Folders.Other,
			Spam:          cfg.Folders.Spam,
		},
		classifier: classifier,
		stopCh:     make(chan struct{}),
		processNow: make(chan struct{}, 1),
	}
}

// Start begins the background worker loop
func (w *Worker) Start() {
	w.mu.Lock()
	if w.stopped {
		w.mu.Unlock()
		return
	}
	w.mu.Unlock()

	// Create new stop channel if old one was closed
	w.mu.Lock()
	select {
	case <-w.stopCh:
		w.stopCh = make(chan struct{})
	default:
	}
	w.mu.Unlock()

	w.wg.Add(1)
	go w.mainLoop()
	log.Println("Worker: started")
}

// Stop signals the worker to shut down gracefully
func (w *Worker) Stop() {
	w.mu.Lock()
	if w.stopped {
		w.mu.Unlock()
		return
	}
	w.stopped = true
	w.mu.Unlock()

	close(w.stopCh)
	w.wg.Wait()
	w.userWorkers.Wait()
	log.Println("Worker: stopped")
}

// mainLoop is the top-level worker loop that processes users
func (w *Worker) mainLoop() {
	defer w.wg.Done()

	// Run immediately on start, then poll at the default interval
	const defaultPollInterval = 5 * time.Minute

	ticker := time.NewTicker(defaultPollInterval)
	defer ticker.Stop()

	// Run first iteration immediately
	w.processAllUsers()

	for {
		select {
		case <-ticker.C:
			w.processAllUsers()
		case <-w.processNow:
			w.processAllUsers()
		case <-w.stopCh:
			return
		}
	}
}

// ProcessNow triggers an immediate processing cycle for all users.
// Safe to call from any goroutine.
func (w *Worker) ProcessNow() {
	select {
	case w.processNow <- struct{}{}:
	default:
		// channel full, a cycle is already queued
	}
}

// processAllUsers iterates over all users with AutoStart enabled
func (w *Worker) processAllUsers() {
	settings, err := w.db.GetUsersWithAutoStart()
	if err != nil {
		log.Printf("Worker: failed to get auto-start users: %v", err)
		return
	}

	if len(settings) == 0 {
		return
	}

	for _, s := range settings {
		select {
		case <-w.stopCh:
			return
		default:
		}

		w.userWorkers.Add(1)
		go w.processUser(s)
	}
}

// processUser processes emails for a single user
func (w *Worker) processUser(settings db.MailboxSettings) {
	defer w.userWorkers.Done()

	// Use separate IMAP username if set, otherwise fall back to the email address
	loginUser := settings.IMAPUsername
	if loginUser == "" {
		loginUser = settings.IMAPUser
	}

	imapConfig := imap.Config{
		Host:     settings.IMAPHost,
		Port:     settings.IMAPPort,
		User:     loginUser,
		Password: settings.IMAPPassEncrypted, // Already decrypted by GetUsersWithAutoStart
		TLS:      settings.IMAPTLS,
	}

	client := imap.NewClient(imapConfig)

	if err := client.Connect(); err != nil {
		log.Printf("Worker [user %d]: IMAP connect failed: %v", settings.UserID, err)
		return
	}
	defer client.Close()

	// Ensure target folders exist
	if err := client.EnsureFolders(w.folders.foldersList()); err != nil {
		log.Printf("Worker [user %d]: ensure folders failed: %v", settings.UserID, err)
		// Non-fatal; continue processing
	}

	// Determine mode: catch-up if last_processed_uid is 0, else steady-state
	if settings.LastProcessedUID == 0 {
		w.runCatchUp(client, settings)
	} else {
		w.runSteadyState(client, settings)
	}
}

// runSteadyState processes all emails in INBOX by UID range.
// Uses FetchBatch (all messages, seen or unseen) so no email is ever skipped.
func (w *Worker) runSteadyState(cl *imap.Client, settings db.MailboxSettings) {
	batchSize := w.cfg.Worker.SteadyStateBatchSize
	if settings.BatchSize > 0 {
		batchSize = settings.BatchSize
	}

	startUID := settings.LastProcessedUID + 1
	if startUID == 0 {
		startUID = 1
	}

	_, emails, err := cl.FetchBatch("INBOX", startUID, math.MaxUint32, batchSize)
	if err != nil {
		log.Printf("Worker [user %d]: fetch batch failed: %v", settings.UserID, err)
		return
	}

	if len(emails) == 0 {
		return
	}

	log.Printf("Worker [user %d]: processing %d emails from INBOX (UID %d+)",
		settings.UserID, len(emails), startUID)

	w.processEmails(cl, settings, emails)
}

// runCatchUp processes all emails from last_processed_uid to latest
// Catch-up mode: fetch in batches of catch_up_batch_size with cooldown
func (w *Worker) runCatchUp(cl *imap.Client, settings db.MailboxSettings) {
	batchSize := w.cfg.Worker.CatchUpBatchSize
	cooldown := time.Duration(w.cfg.Worker.CatchUpCooldownSeconds) * time.Second

	startUID := settings.LastProcessedUID
	if startUID == 0 {
		startUID = 1 // Start from the beginning
	}

	for {
		select {
		case <-w.stopCh:
			return
		default:
		}

		// Fetch the next batch
		_, emails, err := cl.FetchBatch("INBOX", startUID, math.MaxUint32, batchSize)
		if err != nil {
			log.Printf("Worker [user %d]: catch-up fetch failed: %v", settings.UserID, err)
			return
		}

		if len(emails) == 0 {
			return // No more emails to process
		}

		w.processEmails(cl, settings, emails)

		// Update start UID for next batch
		lastUID := emails[len(emails)-1].UID
		startUID = lastUID + 1

		// Cooldown between batches
		if cooldown > 0 {
			time.Sleep(cooldown)
		}
	}
}

// processEmails classifies and moves a batch of emails
func (w *Worker) processEmails(cl *imap.Client, settings db.MailboxSettings, emails []imap.EmailSummary) {
	for _, email := range emails {
		select {
		case <-w.stopCh:
			return
		default:
		}

		w.processSingleEmail(cl, settings, email)
	}
}

// processSingleEmail classifies a single email and moves it
func (w *Worker) processSingleEmail(cl *imap.Client, settings db.MailboxSettings, email imap.EmailSummary) {
	// Check stop signal
	select {
	case <-w.stopCh:
		return
	default:
	}

	// Classify the email
	folder, confidence, err := w.classifier.Classify(email)
	if err != nil {
		log.Printf("Worker [user %d]: classification failed for UID %d: %v",
			settings.UserID, email.UID, err)

		// Record the failure (keep email in INBOX)
		record := &db.ProcessedEmail{
			UserID:          settings.UserID,
			MessageID:       email.MessageID,
			UID:             email.UID,
			From:            email.From,
			Subject:         email.Subject,
			ReceivedDate:    email.Date,
			ClassifiedFolder: "INBOX", // Not moved
			Error:           fmt.Sprintf("classification failed: %v", err),
			TestMode:        settings.TestMode,
		}

		if dbErr := w.db.CreateProcessedEmail(record); dbErr != nil {
			log.Printf("Worker [user %d]: failed to record error for UID %d: %v",
				settings.UserID, email.UID, dbErr)
		}
		return
	}

	// In test mode, just log and record (don't actually move)
	if settings.TestMode {
		log.Printf("Worker [user %d] [TEST] classified UID %d -> %s (confidence: %d%%)",
			settings.UserID, email.UID, folder, confidence)

		record := &db.ProcessedEmail{
			UserID:           settings.UserID,
			MessageID:        email.MessageID,
			UID:              email.UID,
			From:             email.From,
			Subject:          email.Subject,
			ReceivedDate:     email.Date,
			ClassifiedFolder: folder,
			ConfidenceScore:  confidence,
			Moved:            false,
			TestMode:         true,
		}

		if dbErr := w.db.CreateProcessedEmail(record); dbErr != nil {
			log.Printf("Worker [user %d]: failed to record test result for UID %d: %v",
				settings.UserID, email.UID, dbErr)
		}

		// Still update last_processed_uid in test mode
		_ = w.db.UpdateLastProcessedUID(settings.UserID, email.UID)
		return
	}

	// Move the email
	if err := cl.MoveMessages("INBOX", []uint32{email.UID}, folder); err != nil {
		log.Printf("Worker [user %d]: failed to move UID %d to %q: %v",
			settings.UserID, email.UID, folder, err)

		// Record the failure
		record := &db.ProcessedEmail{
			UserID:           settings.UserID,
			MessageID:        email.MessageID,
			UID:              email.UID,
			From:             email.From,
			Subject:          email.Subject,
			ReceivedDate:     email.Date,
			ClassifiedFolder: folder,
			ConfidenceScore:  confidence,
			Error:            fmt.Sprintf("move failed: %v", err),
			TestMode:         false,
		}

		if dbErr := w.db.CreateProcessedEmail(record); dbErr != nil {
			log.Printf("Worker [user %d]: failed to record move error for UID %d: %v",
				settings.UserID, email.UID, dbErr)
		}
		return
	}

	// Record success
	record := &db.ProcessedEmail{
		UserID:           settings.UserID,
		MessageID:        email.MessageID,
		UID:              email.UID,
		From:             email.From,
		Subject:          email.Subject,
		ReceivedDate:     email.Date,
		ClassifiedFolder: folder,
		ConfidenceScore:  confidence,
		Moved:            true,
		TestMode:         false,
	}

	if dbErr := w.db.CreateProcessedEmail(record); dbErr != nil {
		log.Printf("Worker [user %d]: failed to record success for UID %d: %v",
			settings.UserID, email.UID, dbErr)
	}

	// Update last processed UID
	_ = w.db.UpdateLastProcessedUID(settings.UserID, email.UID)

	log.Printf("Worker [user %d]: moved UID %d -> %s (confidence: %d%%)",
		settings.UserID, email.UID, folder, confidence)
}
