package pop3

import (
	"context"
	"fmt"
	"log/slog"
	"strings"
	"time"

	"archivmail/internal/index"
	"archivmail/internal/storage"
	"archivmail/pkg/mailparser"
)

// Importer runs background POP3 import jobs.
type Importer struct {
	store     *Store
	mailStore *storage.Store
	idxMgr    index.TenantIndexer
	logger    *slog.Logger
	TenantID  *int64 // optional tenant assignment for stored mails
	// PROJ-44: optional OCR enqueue hook, wired from main.go.
	ocrSubmit func(mailID string, tenantID *int64)
}

// NewImporter creates a new Importer wired to the storage and index backends.
func NewImporter(store *Store, mailStore *storage.Store, idxMgr index.TenantIndexer, logger *slog.Logger) *Importer {
	return &Importer{
		store:     store,
		mailStore: mailStore,
		idxMgr:    idxMgr,
		logger:    logger,
	}
}

// SetOCRSubmit installs a non-blocking callback that enqueues a mail for
// OCR processing. See imap.Importer.SetOCRSubmit for rationale (PROJ-44).
func (imp *Importer) SetOCRSubmit(fn func(mailID string, tenantID *int64)) {
	imp.ocrSubmit = fn
}

// Run performs a full POP3 import for the given account. It is designed to be
// called as a goroutine: go imp.Run(context.Background(), accountID)
func (imp *Importer) Run(ctx context.Context, accountID int64) {
	log := imp.logger.With("component", "pop3-importer", "account_id", accountID)

	acc, err := imp.store.Get(ctx, accountID)
	if err != nil {
		log.Error("failed to get account", "err", err)
		return
	}

	password, err := imp.store.GetPassword(ctx, accountID)
	if err != nil {
		log.Error("failed to decrypt password", "err", err)
		_ = imp.store.UpdateStatus(ctx, accountID, "error", "failed to decrypt password", 0, 0)
		return
	}

	// Mark as running
	if err := imp.store.UpdateStatus(ctx, accountID, "running", "", 0, 0); err != nil {
		log.Error("failed to update status", "err", err)
		return
	}

	imported, err := imp.doImport(ctx, acc, password, log)
	if err != nil {
		log.Error("import failed", "err", err)
		_ = imp.store.UpdateStatus(ctx, accountID, "error", err.Error(), 0, 0)
		return
	}

	if err := imp.store.UpdateDone(ctx, accountID, imported); err != nil {
		log.Error("failed to update done", "err", err)
	}

	log.Info("import completed", "imported", imported)
}

// doImport handles the actual POP3 connection and message retrieval.
func (imp *Importer) doImport(ctx context.Context, acc *Account, password string, log *slog.Logger) (int, error) {
	c, err := Dial(acc.Host, acc.Port, acc.TLS, acc.TLSSkipVerify)
	if err != nil {
		return 0, fmt.Errorf("pop3 connect: %w", err)
	}
	defer c.Close()

	if err := c.Login(acc.Username, password); err != nil {
		return 0, fmt.Errorf("pop3 login: %w", err)
	}

	total, _, err := c.Stat()
	if err != nil {
		return 0, fmt.Errorf("pop3 stat: %w", err)
	}

	nums, err := c.List()
	if err != nil {
		return 0, fmt.Errorf("pop3 list: %w", err)
	}

	log.Info("starting pop3 import", "total", total, "listed", len(nums))
	_ = imp.store.UpdateStatus(ctx, acc.ID, "running", "", 0, len(nums))

	imported := 0
	for i, num := range nums {
		// Check context cancellation
		select {
		case <-ctx.Done():
			_ = c.Quit()
			return imported, ctx.Err()
		default:
		}

		raw, err := c.Retr(num)
		if err != nil {
			log.Warn("failed to retrieve message, skipping", "msg_num", num, "err", err)
			_ = imp.store.UpdateStatus(ctx, acc.ID, "running", "", i+1, len(nums))
			continue
		}

		if err := imp.storeAndIndex(raw, acc.ID, log); err != nil {
			log.Warn("failed to store/index message, skipping", "msg_num", num, "err", err)
		} else {
			imported++
		}

		_ = imp.store.UpdateStatus(ctx, acc.ID, "running", "", i+1, len(nums))
	}

	_ = c.Quit()
	return imported, nil
}

// storeAndIndex saves a raw email to storage and indexes it.
// accountID identifies the POP3 account for PROJ-52 source tracking.
func (imp *Importer) storeAndIndex(raw []byte, accountID int64, log *slog.Logger) error {
	ctx := context.Background()
	// Save to file storage (deduplicates by SHA256 automatically)
	id, err := imp.mailStore.Save(ctx, raw, time.Now(), imp.TenantID)
	if err != nil {
		return fmt.Errorf("pop3 save: %w", err)
	}

	// PROJ-52: record ingestion source (pop3:<account_id>). Non-fatal.
	accID := accountID
	if err := imp.mailStore.TagSource(ctx, id, "pop3", &accID); err != nil {
		log.Warn("failed to tag source", "id", id, "err", err)
	}

	// Parse for indexing
	pm, err := mailparser.Parse(raw)
	if err != nil {
		log.Warn("failed to parse mail for indexing", "id", id, "err", err)
		// Store succeeded — skip indexing for unparseable mails
		return nil
	}

	// Build attachment names string
	var attachNames []string
	for _, a := range pm.Attachments {
		if a.Filename != "" {
			attachNames = append(attachNames, a.Filename)
		}
	}

	doc := index.MailDocument{
		ID:            id,
		From:          pm.From,
		To:            strings.Join(pm.To, ", "),
		CC:            strings.Join(pm.CC, ", "),
		Subject:       pm.Subject,
		Body:          pm.TextBody,
		AttachNames:   strings.Join(attachNames, " "),
		HasAttachment: len(pm.Attachments) > 0,
		Date:          pm.Date,
		Size:          int64(len(raw)),
		TenantID:      imp.TenantID,
	}

	if err := imp.idxMgr.ForTenant(imp.TenantID).IndexSync(doc); err != nil {
		log.Warn("failed to index mail", "id", id, "err", err)
		// Non-fatal: mail is stored, just not searchable yet
	}

	// PROJ-44: enqueue OCR job for any mail with attachments.
	if imp.ocrSubmit != nil && len(pm.Attachments) > 0 {
		imp.ocrSubmit(id, imp.TenantID)
	}

	return nil
}
