package imap

import (
	"fmt"
	"io"
	"time"

	"github.com/emersion/go-imap"
	"github.com/emersion/go-imap/client"
)

// EmailSummary holds the key fields for classification
type EmailSummary struct {
	UID       uint32
	Subject   string
	From      string
	Date      time.Time
	MessageID string
	Body      string // body text (up to 4000 chars) for AI classification
}

// fetchItems returns the common FetchItems for fetching email metadata + body snippet
func fetchItems() []imap.FetchItem {
	bodySection := &imap.BodySectionName{Peek: true}
	return []imap.FetchItem{
		imap.FetchEnvelope,
		imap.FetchInternalDate,
		imap.FetchFlags,
		bodySection.FetchItem(),
	}
}

// bodySections returns a list of body section names to peek at body content
func bodySection() *imap.BodySectionName {
	return &imap.BodySectionName{Peek: true}
}

// buildEmailSummary converts an imap.Message to EmailSummary
func buildEmailSummary(msg *imap.Message) EmailSummary {
	summary := EmailSummary{
		UID:       msg.Uid,
		Date:      msg.InternalDate,
		MessageID: msg.Envelope.MessageId,
		Subject:   msg.Envelope.Subject,
	}

	if len(msg.Envelope.From) > 0 {
		summary.From = msg.Envelope.From[0].Address()
	}

	// Extract body text from first available body section
	// Limit to 4000 chars for AI classification efficiency
	for _, literal := range msg.Body {
		if literal != nil {
			data, err := io.ReadAll(literal)
			if err == nil && len(data) > 0 {
				if len(data) > 4000 {
					summary.Body = string(data[:4000])
				} else {
					summary.Body = string(data)
				}
			}
			break // first body section only
		}
	}

	return summary
}

// FetchUnseen fetches unseen messages from the selected mailbox
// Returns the mailbox status and a slice of email summaries
func (c *Client) FetchUnseen(mailbox string) (*imap.MailboxStatus, []EmailSummary, error) {
	var (
		emails []EmailSummary
		mbox   *imap.MailboxStatus
	)

	err := c.Execute(func(cl *client.Client) error {
		var err error
		mbox, err = cl.Select(mailbox, true)
		if err != nil {
			return fmt.Errorf("select %q: %w", mailbox, err)
		}

		// Search for unseen messages
		criteria := imap.NewSearchCriteria()
		criteria.WithoutFlags = []string{imap.SeenFlag}

		uids, err := cl.UidSearch(criteria)
		if err != nil {
			return fmt.Errorf("uid search: %w", err)
		}

		if len(uids) == 0 {
			return nil
		}

		// Fetch envelope + body snippet for each unseen message
		seqset := new(imap.SeqSet)
		seqset.AddNum(uids...)

		messages := make(chan *imap.Message, 10)
		done := make(chan error, 1)

		go func() {
			done <- cl.UidFetch(seqset, fetchItems(), messages)
		}()

		for msg := range messages {
			emails = append(emails, buildEmailSummary(msg))
		}

		if err := <-done; err != nil {
			return fmt.Errorf("uid fetch: %w", err)
		}

		return nil
	})

	if err != nil {
		return nil, nil, err
	}

	return mbox, emails, nil
}

// FetchBatch fetches a batch of messages by UID range
// Used for catch-up mode: from lastProcessedUID+1 to latest
// Returns a bounded number of messages (batchSize) sorted ascending
func (c *Client) FetchBatch(mailbox string, startUID, maxUID uint32, batchSize int) (*imap.MailboxStatus, []EmailSummary, error) {
	var (
		emails []EmailSummary
		mbox   *imap.MailboxStatus
	)

	err := c.Execute(func(cl *client.Client) error {
		var err error
		mbox, err = cl.Select(mailbox, true)
		if err != nil {
			return fmt.Errorf("select %q: %w", mailbox, err)
		}

		// Fetch messages in UID range
		criteria := imap.NewSearchCriteria()
		criteria.Uid = new(imap.SeqSet)
		criteria.Uid.AddRange(startUID, maxUID)

		uids, err := cl.UidSearch(criteria)
		if err != nil {
			return fmt.Errorf("uid search: %w", err)
		}

		if len(uids) == 0 {
			return nil
		}

		// Apply batch size limit
		batch := uids
		if len(batch) > batchSize {
			batch = batch[:batchSize]
		}

		seqset := new(imap.SeqSet)
		seqset.AddNum(batch...)

		messages := make(chan *imap.Message, 10)
		done := make(chan error, 1)

		go func() {
			done <- cl.UidFetch(seqset, fetchItems(), messages)
		}()

		for msg := range messages {
			emails = append(emails, buildEmailSummary(msg))
		}

		return <-done
	})

	if err != nil {
		return nil, nil, err
	}

	return mbox, emails, nil
}

// SelectMailbox selects a mailbox and returns its status
func (c *Client) SelectMailbox(name string, readOnly bool) (*imap.MailboxStatus, error) {
	var mbox *imap.MailboxStatus

	err := c.Execute(func(cl *client.Client) error {
		var err error
		mbox, err = cl.Select(name, readOnly)
		return err
	})

	if err != nil {
		return nil, fmt.Errorf("failed to select mailbox %q: %w", name, err)
	}

	return mbox, nil
}

// CountUnseen returns the number of unseen messages in the mailbox
func (c *Client) CountUnseen(mailbox string) (uint32, error) {
	var mbox *imap.MailboxStatus

	err := c.Execute(func(cl *client.Client) error {
		var err error
		mbox, err = cl.Select(mailbox, true)
		return err
	})

	if err != nil {
		return 0, err
	}

	return mbox.Unseen, nil
}

// FetchByUIDs fetches specific messages by their UIDs
func (c *Client) FetchByUIDs(mailbox string, uids []uint32) ([]EmailSummary, error) {
	var summaries []EmailSummary

	err := c.Execute(func(cl *client.Client) error {
		_, err := cl.Select(mailbox, true)
		if err != nil {
			return err
		}

		seqset := new(imap.SeqSet)
		seqset.AddNum(uids...)

		messages := make(chan *imap.Message, 10)
		done := make(chan error, 1)

		go func() {
			done <- cl.UidFetch(seqset, fetchItems(), messages)
		}()

		for msg := range messages {
			summaries = append(summaries, buildEmailSummary(msg))
		}

		return <-done
	})

	if err != nil {
		return nil, err
	}

	return summaries, nil
}

// FetchBodySection fetches a specific body section for a single message
func (c *Client) FetchBodySection(mailbox string, uid uint32, section *imap.BodySectionName) ([]byte, error) {
	var body []byte

	err := c.Execute(func(cl *client.Client) error {
		_, err := cl.Select(mailbox, true)
		if err != nil {
			return err
		}

		seqset := new(imap.SeqSet)
		seqset.AddNum(uid)

		messages := make(chan *imap.Message, 1)
		done := make(chan error, 1)

		go func() {
			done <- cl.UidFetch(seqset, []imap.FetchItem{
				section.FetchItem(),
			}, messages)
		}()

		for msg := range messages {
			for _, bs := range msg.Body {
				if bs != nil {
					body, err = io.ReadAll(bs)
					if err != nil {
						return fmt.Errorf("read body: %w", err)
					}
					break
				}
			}
		}

		if err := <-done; err != nil {
			return fmt.Errorf("uid fetch body: %w", err)
		}

		return nil
	})

	if err != nil {
		return nil, err
	}

	return body, nil
}

// EnsureFolders creates folders if they don't exist
func (c *Client) EnsureFolders(folders []string) error {
	return c.Execute(func(cl *client.Client) error {
		mailboxes := make(chan *imap.MailboxInfo, 50)
		done := make(chan error, 1)

		go func() {
			done <- cl.List("", "*", mailboxes)
		}()

		existing := make(map[string]bool)
		for m := range mailboxes {
			existing[m.Name] = true
		}

		if err := <-done; err != nil {
			return fmt.Errorf("failed to list mailboxes: %w", err)
		}

		for _, folder := range folders {
			if !existing[folder] {
				if err := cl.Create(folder); err != nil {
					return fmt.Errorf("failed to create folder %q: %w", folder, err)
				}
			}
		}

		return nil
	})
}
