package imap

import (
	"crypto/tls"
	"fmt"
	"sync"
	"time"

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

// Config holds IMAP server connection settings
type Config struct {
	Host     string
	Port     int
	User     string
	Password string
	TLS      bool
}

// Client wraps go-imap client with connection management
type Client struct {
	config Config
	mu     sync.Mutex
	client *client.Client
}

// NewClient creates a new IMAP client wrapper
func NewClient(config Config) *Client {
	return &Client{config: config}
}

// Connect establishes a connection to the IMAP server and logs in
func (c *Client) Connect() error {
	c.mu.Lock()
	defer c.mu.Unlock()

	return c.connect()
}

// connect performs the actual connection (must hold mu)
func (c *Client) connect() error {
	// Close existing connection if any
	c.close()

	addr := fmt.Sprintf("%s:%d", c.config.Host, c.config.Port)

	var (
		cl *client.Client
		err error
	)

	if c.config.TLS {
		// TLS connection (port 993)
		cl, err = client.DialTLS(addr, &tls.Config{
			ServerName: c.config.Host,
		})
	} else {
		// Plain connection (port 143) then STARTTLS
		cl, err = client.Dial(addr)
		if err == nil {
			err = cl.StartTLS(&tls.Config{
				ServerName: c.config.Host,
			})
		}
	}

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

	// Login
	if err := cl.Login(c.config.User, c.config.Password); err != nil {
		cl.Logout()
		return fmt.Errorf("failed to login: %w", err)
	}

	// Set timeout
	cl.Timeout = 30 * time.Second

	c.client = cl
	return nil
}

// EnsureConnected checks if connected and reconnects if needed
func (c *Client) EnsureConnected() error {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.client == nil || c.client.State() == imap.LogoutState {
		return c.connect()
	}

	// Test connection with NOOP
	if err := c.client.Noop(); err != nil {
		return c.connect()
	}

	return nil
}

// close closes the connection (must hold mu)
func (c *Client) close() {
	if c.client != nil {
		c.client.Logout()
		c.client = nil
	}
}

// Close closes the IMAP connection
func (c *Client) Close() error {
	c.mu.Lock()
	defer c.mu.Unlock()

	c.close()
	return nil
}

// Execute runs a function with a connected client, reconnecting if needed
func (c *Client) Execute(fn func(cl *client.Client) error) error {
	c.mu.Lock()
	cl := c.client
	c.mu.Unlock()

	if cl == nil {
		if err := c.EnsureConnected(); err != nil {
			return err
		}
		c.mu.Lock()
		cl = c.client
		c.mu.Unlock()
	}

	// Attempt the operation
	err := fn(cl)
	if err != nil {
		// Connection might have dropped, try to reconnect and retry once
		if connErr := c.EnsureConnected(); connErr != nil {
			return fmt.Errorf("operation failed and reconnection failed: %v (original: %v)", connErr, err)
		}

		c.mu.Lock()
		cl = c.client
		c.mu.Unlock()

		// Retry once
		return fn(cl)
	}

	return err
}

// LoggedOut returns a channel that closes when the connection drops
func (c *Client) LoggedOut() <-chan struct{} {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.client != nil {
		return c.client.LoggedOut()
	}

	ch := make(chan struct{})
	close(ch)
	return ch
}

// IsConnected returns whether the client is connected
func (c *Client) IsConnected() bool {
	c.mu.Lock()
	defer c.mu.Unlock()

	return c.client != nil && c.client.State() != imap.LogoutState
}