package main

import (
	"crypto/tls"
	"fmt"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/emersion/go-imap"
	"github.com/emersion/go-imap/client"
	"github.com/emersion/go-sasl"
	"github.com/sirupsen/logrus"
	bolt "go.etcd.io/bbolt"
)

// IMAPConfig holds connection details
type IMAPConfig struct {
	Host     string
	Port     int
	Username string
	Password string
	UseTLS   bool
}

// parseIMAPURL parses "user:pass@host:port" format
func parseIMAPURL(rawURL string) (*IMAPConfig, error) {
	if !strings.Contains(rawURL, "://") {
		rawURL = "imap://" + rawURL
	}

	u, err := url.Parse(rawURL)
	if err != nil {
		return nil, err
	}

	port := 993 // default IMAPS
	if u.Port() != "" {
		port, err = strconv.Atoi(u.Port())
		if err != nil {
			return nil, err
		}
	}

	password, _ := u.User.Password()
	// URL decode the password to handle special characters
	if password != "" {
		if decodedPassword, err := url.QueryUnescape(password); err == nil {
			password = decodedPassword
		}
	}

	username := u.User.Username()
	// URL decode the username to handle special characters
	if username != "" {
		if decodedUsername, err := url.QueryUnescape(username); err == nil {
			username = decodedUsername
		}
	}

	return &IMAPConfig{
		Host:     u.Hostname(),
		Port:     port,
		Username: username,
		Password: password,
		UseTLS:   port == 993 || u.Scheme == "imaps",
	}, nil
}

func connectIMAP(cfg *IMAPConfig, insecure bool) (*client.Client, error) {
	addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)

	// Debug logging (mask password for security)
	maskedPassword := strings.Repeat("*", len(cfg.Password))
	logrus.Debugf("Connecting to %s with username: %s, password: %s", addr, cfg.Username, maskedPassword)

	var c *client.Client
	var err error

	if cfg.UseTLS {
		tlsConfig := &tls.Config{
			ServerName:         cfg.Host,
			InsecureSkipVerify: insecure,
		}
		c, err = client.DialTLS(addr, tlsConfig)
	} else {
		c, err = client.Dial(addr)
	}

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

	// Check server capabilities first
	caps, errCaps := c.Capability()
	if errCaps != nil {
		logrus.Debugf("Failed to get capabilities: %v", errCaps)
		caps = make(map[string]bool) // fallback to empty map
	} else {
		logrus.Debugf("Server capabilities: %v", caps)
	}

	// Try LOGIN first
	if err := c.Login(cfg.Username, cfg.Password); err != nil {
		logrus.Debugf("LOGIN failed: %v", err)

		// Try AUTHENTICATE PLAIN as fallback
		if caps["AUTH=PLAIN"] || caps["AUTH=PLAIN-CLIENT-FIRST"] {
			logrus.Debugf("Trying AUTHENTICATE PLAIN")
			auth := sasl.NewPlainClient("", cfg.Username, cfg.Password)
			if authErr := c.Authenticate(auth); authErr != nil {
				c.Close()
				return nil, fmt.Errorf("login failed: %w (also tried AUTHENTICATE PLAIN: %v)", err, authErr)
			}
			logrus.Debugf("AUTHENTICATE PLAIN succeeded")
		} else {
			c.Close()
			return nil, fmt.Errorf("login failed: %w", err)
		}
	} else {
		logrus.Debugf("LOGIN succeeded")
	}

	logrus.Infof("Connected to %s as %s", addr, cfg.Username)
	return c, nil
}

// listMailboxes returns all mailboxes
func listMailboxes(c *client.Client) ([]*imap.MailboxInfo, error) {
	mailboxes := make(chan *imap.MailboxInfo, 10)
	done := make(chan error, 1)

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

	var result []*imap.MailboxInfo
	for m := range mailboxes {
		result = append(result, m)
	}

	if err := <-done; err != nil {
		return nil, err
	}

	return result, nil
}

func copyMessages(srcClient, dstClient *client.Client, srcMbox, dstMbox string, stateKey string, db *bolt.DB) error {
	// Select source mailbox
	srcStatus, err := srcClient.Select(srcMbox, true) // read-only
	if err != nil {
		return fmt.Errorf("select source %s: %w", srcMbox, err)
	}

	// Select destination mailbox
	_, err = dstClient.Select(dstMbox, false)
	if err != nil {
		// Try to create mailbox if it doesn't exist
		if err := dstClient.Create(dstMbox); err != nil {
			return fmt.Errorf("create dest %s: %w", dstMbox, err)
		}
		if _, err := dstClient.Select(dstMbox, false); err != nil {
			return fmt.Errorf("select dest %s: %w", dstMbox, err)
		}
	}

	// Get last migrated UID for resume
	var lastUID uint32 = 0
	if db != nil {
		lastUID, _ = getLastUID(db, stateKey)
	}

	logrus.Infof("Migrating %s: %d messages (resume from UID %d)", srcMbox, srcStatus.Messages, lastUID)

	// Search for messages to copy
	criteria := &imap.SearchCriteria{}
	if lastUID > 0 {
		criteria.Uid = &imap.SeqSet{}
		criteria.Uid.AddRange(lastUID+1, 0) // from lastUID+1 to end
	} else {
		criteria.Uid = &imap.SeqSet{}
		criteria.Uid.AddRange(1, 0) // all messages
	}

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

	if len(uids) == 0 {
		logrus.Infof("No new messages to migrate in %s", srcMbox)
		return nil
	}

	logrus.Infof("Found %d messages to migrate", len(uids))

	// Copy messages in batches
	batchSize := 50
	for i := 0; i < len(uids); i += batchSize {
		end := i + batchSize
		if end > len(uids) {
			end = len(uids)
		}

		batch := uids[i:end]
		seqSet := &imap.SeqSet{}
		for _, uid := range batch {
			seqSet.AddNum(uid)
		}

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

		go func() {
			done <- srcClient.UidFetch(seqSet, []imap.FetchItem{imap.FetchRFC822}, messages)
		}()

		// Process each message
		for msg := range messages {
			if len(msg.Body) == 0 {
				logrus.Warnf("Empty message body for UID %d", msg.Uid)
				continue
			}

			// Get message content
			var msgContent []byte
			for _, body := range msg.Body {
				buf := make([]byte, 1024*1024) // 1MB buffer
				n, err := body.Read(buf)
				if err != nil && err.Error() != "EOF" {
					logrus.Errorf("Read message UID %d: %v", msg.Uid, err)
					continue
				}
				msgContent = append(msgContent, buf[:n]...)
			}

			// Append to destination
			if err := dstClient.Append(dstMbox, nil, time.Time{}, strings.NewReader(string(msgContent))); err != nil {
				logrus.Errorf("Append message UID %d: %v", msg.Uid, err)
				continue
			}

			// Update state
			if db != nil {
				// TODO: implement setLastUID from state.go
				// setLastUID(db.(*bolt.DB), stateKey, msg.Uid)
			}

			logrus.Debugf("Copied message UID %d", msg.Uid)
		}

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

		logrus.Infof("Migrated batch %d-%d/%d", i+1, end, len(uids))
	}

	return nil
}
