// Package email provides functionality for an email inbox with multiple SMTP servers and IMAP clients.
package email

import (
	"context"
	"sync"

	"github.com/abhinavxd/libredesk/internal/inbox"
	"github.com/knadh/smtppool"
	"github.com/zerodha/logf"
)

const (
	ChannelEmail = "email"
)

// Config holds the email inbox configuration with multiple SMTP servers and IMAP clients.
type Config struct {
	SMTP []SMTPConfig `json:"smtp"`
	IMAP []IMAPConfig `json:"imap"`
	From string       `json:"from"`
}

// SMTPConfig represents an SMTP server's credentials with the smtppool options.
type SMTPConfig struct {
	Username      string            `json:"username"`
	Password      string            `json:"password"`
	AuthProtocol  string            `json:"auth_protocol"`
	TLSType       string            `json:"tls_type"`
	TLSSkipVerify bool              `json:"tls_skip_verify"`
	EmailHeaders  map[string]string `json:"email_headers"`
	smtppool.Opt  `json:",squash"`  // SMTP pool options.
}

// IMAPConfig holds IMAP client credentials and configuration.
type IMAPConfig struct {
	Host           string `json:"host"`
	Port           int    `json:"port"`
	Username       string `json:"username"`
	Password       string `json:"password"`
	Mailbox        string `json:"mailbox"`
	ReadInterval   string `json:"read_interval"`
	ScanInboxSince string `json:"scan_inbox_since"`
	TLSType        string `json:"tls_type"`
	TLSSkipVerify  bool   `json:"tls_skip_verify"`
}

// Email represents the email inbox with multiple SMTP servers and IMAP clients.
type Email struct {
	id           int
	smtpPools    []*smtppool.Pool
	imapCfg      []IMAPConfig
	headers      map[string]string
	lo           *logf.Logger
	from         string
	messageStore inbox.MessageStore
	userStore    inbox.UserStore
	wg           sync.WaitGroup
}

// Opts holds the options required for the email inbox.
type Opts struct {
	ID      int
	Headers map[string]string
	Config  Config
	Lo      *logf.Logger
}

// New returns a new instance of the email inbox.
func New(store inbox.MessageStore, userStore inbox.UserStore, opts Opts) (*Email, error) {
	pools, err := NewSmtpPool(opts.Config.SMTP)
	if err != nil {
		return nil, err
	}
	e := &Email{
		id:           opts.ID,
		headers:      opts.Headers,
		from:         opts.Config.From,
		imapCfg:      opts.Config.IMAP,
		lo:           opts.Lo,
		smtpPools:    pools,
		messageStore: store,
		userStore:    userStore,
	}
	return e, nil
}

// Identifier returns the unique identifier of the inbox which is the database ID.
func (e *Email) Identifier() int {
	return e.id
}

// Receive starts reading incoming messages for each IMAP client.
func (e *Email) Receive(ctx context.Context) error {
	for _, cfg := range e.imapCfg {
		e.wg.Add(1)
		go func(cfg IMAPConfig) {
			defer e.wg.Done()
			if err := e.ReadIncomingMessages(ctx, cfg); err != nil {
				e.lo.Error("error reading incoming messages", "error", err)
			}
		}(cfg)
	}
	e.wg.Wait()
	return nil
}

// Close cloes email channel by closing the smtp pool
func (e *Email) Close() error {
	return e.closeSMTPPool()
}

// FromAddress returns the from address for this inbox.
func (e *Email) FromAddress() string {
	return e.from
}

// Channel returns the channel name for this inbox.
func (e *Email) Channel() string {
	return ChannelEmail
}

// closeSMTPPool closes the smtp pool.
func (e *Email) closeSMTPPool() error {
	for _, p := range e.smtpPools {
		p.Close()
	}
	return nil
}
