package mail

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"mime"
	"strings"
	"time"

	"github.com/emersion/go-imap/v2"
	"github.com/emersion/go-imap/v2/imapclient"
	"github.com/emersion/go-message"
	"mail2couch/config"
)

// ImapClient wraps the IMAP client
type ImapClient struct {
	*imapclient.Client
}

// Message represents an email message retrieved from IMAP
type Message struct {
	UID         uint32
	From        []string
	To          []string
	Subject     string
	Date        time.Time
	Body        string
	Headers     map[string][]string
	Attachments []Attachment
}

// Attachment represents an email attachment
type Attachment struct {
	Filename    string
	ContentType string
	Content     []byte
}

// NewImapClient creates a new IMAP client from the configuration
func NewImapClient(source *config.MailSource) (*ImapClient, error) {
	addr := fmt.Sprintf("%s:%d", source.Host, source.Port)

	var client *imapclient.Client
	var err error

	// Try TLS first for standard IMAPS ports (993, 465)
	if source.Port == 993 || source.Port == 465 {
		client, err = imapclient.DialTLS(addr, nil)
	} else {
		// Use insecure connection for other ports (143, 3143, etc.)
		client, err = imapclient.DialInsecure(addr, nil)
	}

	if err != nil {
		return nil, fmt.Errorf("failed to dial IMAP server: %w", err)
	}

	if err := client.Login(source.User, source.Password).Wait(); err != nil {
		return nil, fmt.Errorf("failed to login: %w", err)
	}

	return &ImapClient{client}, nil
}

// ListMailboxes lists all available mailboxes
func (c *ImapClient) ListMailboxes() ([]string, error) {
	var mailboxes []string
	cmd := c.List("", "*", nil)

	infos, err := cmd.Collect()
	if err != nil {
		return nil, err
	}

	for _, info := range infos {
		mailboxes = append(mailboxes, info.Mailbox)
	}

	return mailboxes, nil
}

// ListFilteredMailboxes lists mailboxes matching the given folder filters using IMAP LIST
func (c *ImapClient) ListFilteredMailboxes(filter *config.FolderFilter) ([]string, error) {
	var allMailboxes []string
	
	// If no include patterns, get all mailboxes
	if len(filter.Include) == 0 {
		return c.ListMailboxes()
	}
	
	// Use IMAP LIST with each include pattern to let the server filter
	seen := make(map[string]bool)
	for _, pattern := range filter.Include {
		cmd := c.List("", pattern, nil)
		infos, err := cmd.Collect()
		if err != nil {
			log.Printf("Failed to list mailboxes with pattern '%s': %v", pattern, err)
			continue
		}
		
		for _, info := range infos {
			if !seen[info.Mailbox] {
				allMailboxes = append(allMailboxes, info.Mailbox)
				seen[info.Mailbox] = true
			}
		}
	}
	
	// Apply exclude filters client-side (IMAP LIST doesn't support exclusion)
	if len(filter.Exclude) == 0 {
		return allMailboxes, nil
	}
	
	var filteredMailboxes []string
	for _, mailbox := range allMailboxes {
		excluded := false
		for _, excludePattern := range filter.Exclude {
			if matched := c.matchesImapPattern(excludePattern, mailbox); matched {
				excluded = true
				break
			}
		}
		if !excluded {
			filteredMailboxes = append(filteredMailboxes, mailbox)
		}
	}
	
	return filteredMailboxes, nil
}

// matchesImapPattern matches IMAP-style patterns (simple * wildcard matching)
func (c *ImapClient) matchesImapPattern(pattern, name string) bool {
	// Handle exact match
	if pattern == name {
		return true
	}
	
	// Handle simple prefix wildcard: "Work*" should match "Work/Projects"
	if strings.HasSuffix(pattern, "*") && !strings.Contains(pattern[:len(pattern)-1], "*") {
		prefix := strings.TrimSuffix(pattern, "*")
		return strings.HasPrefix(name, prefix)
	}
	
	// Handle simple suffix wildcard: "*Temp" should match "Work/Temp"  
	if strings.HasPrefix(pattern, "*") && !strings.Contains(pattern[1:], "*") {
		suffix := strings.TrimPrefix(pattern, "*")
		return strings.HasSuffix(name, suffix)
	}
	
	// Handle contains wildcard: "*Temp*" should match "Work/Temp/Archive"
	if strings.HasPrefix(pattern, "*") && strings.HasSuffix(pattern, "*") {
		middle := strings.Trim(pattern, "*")
		return strings.Contains(name, middle)
	}
	
	// For other patterns, fall back to basic string comparison
	return false
}

// GetMessages retrieves messages from a specific mailbox with filtering support
// Returns messages and a map of all current UIDs in the mailbox
// maxMessages: 0 means no limit, > 0 limits the number of messages to fetch
// since: if provided, only fetch messages newer than this date (for incremental sync)
func (c *ImapClient) GetMessages(mailbox string, since *time.Time, maxMessages int, messageFilter *config.MessageFilter) ([]*Message, map[uint32]bool, error) {
	// Select the mailbox
	mbox, err := c.Select(mailbox, nil).Wait()
	if err != nil {
		return nil, nil, fmt.Errorf("failed to select mailbox %s: %w", mailbox, err)
	}

	if mbox.NumMessages == 0 {
		return []*Message{}, make(map[uint32]bool), nil
	}

	var messages []*Message
	currentUIDs := make(map[uint32]bool)

	// First, get all current UIDs in the mailbox for sync purposes
	allUIDsSet := imap.SeqSet{}
	allUIDsSet.AddRange(1, mbox.NumMessages)

	// Fetch UIDs for all messages to track current state
	uidCmd := c.Fetch(allUIDsSet, &imap.FetchOptions{UID: true})
	for {
		msg := uidCmd.Next()
		if msg == nil {
			break
		}

		data, err := msg.Collect()
		if err != nil {
			continue
		}

		if data.UID != 0 {
			currentUIDs[uint32(data.UID)] = true
		}
	}
	uidCmd.Close()

	// Determine which messages to fetch based on since date
	var seqSet imap.SeqSet

	if since != nil {
		// Use IMAP SEARCH to find messages since the specified date
		searchCriteria := &imap.SearchCriteria{
			Since: *since,
		}

		searchCmd := c.Search(searchCriteria, nil)
		searchResults, err := searchCmd.Wait()
		if err != nil {
			log.Printf("IMAP SEARCH failed, falling back to fetch all: %v", err)
			// Fall back to fetching all messages
			numToFetch := mbox.NumMessages
			if maxMessages > 0 && int(numToFetch) > maxMessages {
				numToFetch = uint32(maxMessages)
			}
			seqSet.AddRange(mbox.NumMessages-numToFetch+1, mbox.NumMessages)
		} else {
			// Convert search results to sequence set
			searchSeqNums := searchResults.AllSeqNums()
			if len(searchSeqNums) == 0 {
				return []*Message{}, currentUIDs, nil
			}

			// Limit results if maxMessages is specified
			if maxMessages > 0 && len(searchSeqNums) > maxMessages {
				searchSeqNums = searchSeqNums[len(searchSeqNums)-maxMessages:]
			}

			for _, seqNum := range searchSeqNums {
				seqSet.AddNum(seqNum)
			}
		}
	} else {
		// No since date specified, fetch recent messages up to maxMessages
		numToFetch := mbox.NumMessages
		if maxMessages > 0 && int(numToFetch) > maxMessages {
			numToFetch = uint32(maxMessages)
		}

		if numToFetch == 0 {
			return []*Message{}, currentUIDs, nil
		}

		// Fetch the most recent messages
		seqSet.AddRange(mbox.NumMessages-numToFetch+1, mbox.NumMessages)
	}

	// Fetch message data - get envelope and full message body
	options := &imap.FetchOptions{
		Envelope: true,
		UID:      true,
		BodySection: []*imap.FetchItemBodySection{
			{}, // Empty section gets the entire message
		},
	}

	fetchCmd := c.Fetch(seqSet, options)

	for {
		msg := fetchCmd.Next()
		if msg == nil {
			break
		}

		parsedMsg, err := c.parseMessage(msg)
		if err != nil {
			log.Printf("Failed to parse message: %v", err)
			continue
		}

		// Apply message-level keyword filtering
		if messageFilter != nil && !c.ShouldProcessMessage(parsedMsg, messageFilter) {
			continue // Skip this message due to keyword filter
		}

		messages = append(messages, parsedMsg)
	}

	if err := fetchCmd.Close(); err != nil {
		return nil, nil, fmt.Errorf("failed to fetch messages: %w", err)
	}

	return messages, currentUIDs, nil
}

// parseMessage parses an IMAP fetch response into our Message struct
func (c *ImapClient) parseMessage(fetchMsg *imapclient.FetchMessageData) (*Message, error) {
	msg := &Message{
		UID:         fetchMsg.SeqNum, // Using sequence number for now
		Headers:     make(map[string][]string),
		Attachments: []Attachment{},
	}

	// Collect all fetch data first
	buffer, err := fetchMsg.Collect()
	if err != nil {
		return nil, fmt.Errorf("failed to collect fetch data: %w", err)
	}

	// Parse envelope for basic headers
	if buffer.Envelope != nil {
		env := buffer.Envelope
		msg.Subject = env.Subject
		msg.Date = env.Date

		// Parse From addresses
		for _, addr := range env.From {
			if addr.Mailbox != "" {
				fullAddr := addr.Mailbox
				if addr.Host != "" {
					fullAddr = addr.Mailbox + "@" + addr.Host
				}
				msg.From = append(msg.From, fullAddr)
			}
		}

		// Parse To addresses
		for _, addr := range env.To {
			if addr.Mailbox != "" {
				fullAddr := addr.Mailbox
				if addr.Host != "" {
					fullAddr = addr.Mailbox + "@" + addr.Host
				}
				msg.To = append(msg.To, fullAddr)
			}
		}
	}

	// Get UID if available
	if buffer.UID != 0 {
		msg.UID = uint32(buffer.UID)
	}

	// Parse full message content
	if len(buffer.BodySection) > 0 {
		bodyBuffer := buffer.BodySection[0]
		reader := bytes.NewReader(bodyBuffer.Bytes)

		// Parse the message using go-message
		entity, err := message.Read(reader)
		if err != nil {
			return nil, fmt.Errorf("failed to parse message: %w", err)
		}

		// Extract headers
		header := entity.Header
		for field := header.Fields(); field.Next(); {
			key := field.Key()
			value, _ := field.Text()
			msg.Headers[key] = append(msg.Headers[key], value)
		}

		// Parse message body and attachments
		if err := c.parseMessageBody(entity, msg); err != nil {
			return nil, fmt.Errorf("failed to parse message body: %w", err)
		}
	}

	return msg, nil
}

// parseMessageBody extracts the body and attachments from a message entity
func (c *ImapClient) parseMessageBody(entity *message.Entity, msg *Message) error {
	mediaType, _, err := entity.Header.ContentType()
	if err != nil {
		// Default to text/plain if no content type
		mediaType = "text/plain"
	}

	if strings.HasPrefix(mediaType, "multipart/") {
		// Handle multipart message
		mr := entity.MultipartReader()
		if mr == nil {
			return fmt.Errorf("failed to create multipart reader")
		}

		for {
			part, err := mr.NextPart()
			if err == io.EOF {
				break
			}
			if err != nil {
				return fmt.Errorf("failed to read multipart: %w", err)
			}

			if err := c.parseMessagePart(part, msg); err != nil {
				log.Printf("Failed to parse message part: %v", err)
				// Continue processing other parts
			}
		}
	} else {
		// Handle single part message
		if err := c.parseMessagePart(entity, msg); err != nil {
			return err
		}
	}

	return nil
}

// parseMessagePart processes a single message part (body or attachment)
func (c *ImapClient) parseMessagePart(entity *message.Entity, msg *Message) error {
	mediaType, params, err := entity.Header.ContentType()
	if err != nil {
		mediaType = "text/plain"
	}

	// Get content disposition
	disposition, dispositionParams, _ := entity.Header.ContentDisposition()

	// Determine if this is an attachment
	isAttachment := disposition == "attachment" ||
		(disposition == "inline" && dispositionParams["filename"] != "") ||
		params["name"] != ""

	if isAttachment {
		// Handle attachment
		filename := dispositionParams["filename"]
		if filename == "" {
			filename = params["name"]
		}
		if filename == "" {
			filename = "unnamed_attachment"
		}

		// Decode filename if needed
		decoder := &mime.WordDecoder{}
		filename, _ = decoder.DecodeHeader(filename)

		// Read attachment content
		content, err := io.ReadAll(entity.Body)
		if err != nil {
			return fmt.Errorf("failed to read attachment content: %w", err)
		}

		attachment := Attachment{
			Filename:    filename,
			ContentType: mediaType,
			Content:     content,
		}

		msg.Attachments = append(msg.Attachments, attachment)
	} else if strings.HasPrefix(mediaType, "text/") && msg.Body == "" {
		// Handle text body (only take the first text part as body)
		bodyBytes, err := io.ReadAll(entity.Body)
		if err != nil {
			return fmt.Errorf("failed to read message body: %w", err)
		}
		msg.Body = string(bodyBytes)
	}

	return nil
}


// ShouldProcessMessage checks if a message should be processed based on keyword filters
func (c *ImapClient) ShouldProcessMessage(msg *Message, filter *config.MessageFilter) bool {
	// Check subject keywords
	if len(filter.SubjectKeywords) > 0 {
		if !c.containsAnyKeyword(strings.ToLower(msg.Subject), filter.SubjectKeywords) {
			return false
		}
	}

	// Check sender keywords
	if len(filter.SenderKeywords) > 0 {
		senderMatch := false
		for _, sender := range msg.From {
			if c.containsAnyKeyword(strings.ToLower(sender), filter.SenderKeywords) {
				senderMatch = true
				break
			}
		}
		if !senderMatch {
			return false
		}
	}

	// Check recipient keywords
	if len(filter.RecipientKeywords) > 0 {
		recipientMatch := false
		for _, recipient := range msg.To {
			if c.containsAnyKeyword(strings.ToLower(recipient), filter.RecipientKeywords) {
				recipientMatch = true
				break
			}
		}
		if !recipientMatch {
			return false
		}
	}

	return true
}

// containsAnyKeyword checks if the text contains any of the specified keywords (case-insensitive)
func (c *ImapClient) containsAnyKeyword(text string, keywords []string) bool {
	for _, keyword := range keywords {
		if strings.Contains(text, strings.ToLower(keyword)) {
			return true
		}
	}
	return false
}

// Logout logs the client out
func (c *ImapClient) Logout() {
	if err := c.Client.Logout(); err != nil {
		log.Printf("Failed to logout: %v", err)
	}
}
