package contact

import (
	"crypto/tls"
	"fmt"
	"log"
	"net/mail"
	"net/smtp"
	"os"
	"strings"

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

type Emailer struct {
	From     string
	SMTP     string
	POP3     string
	IMAP     string
	Password string
	Limit    int
}

func NewEmailer() *Emailer {
	envOr := func(key, def string) string {
		if v, ok := os.LookupEnv(strings.ToUpper(key)); ok {
			return v
		}
		return def
	}
	return &Emailer{
		From:     envOr("FROM", "breellocaldev@gmail.com"),
		SMTP:     envOr("SMTP", "smtp.gmail.com:465"),
		Password: envOr("PASSWORD", "lhnjijrvqaesiufp"),
	}
}

func (e *Emailer) Read() (chan *mail.Message, error) {
	return e.ReadIMAP()
}

func (e *Emailer) ReadIMAP() (chan *mail.Message, error) {
	c, err := client.DialTLS(e.IMAP, nil)
	if err != nil {
		return nil, err
	}
	if err := c.Login(e.From, e.Password); err != nil {
		return nil, err
	}
	mbox, err := c.Select("INBOX", true) //readonly
	if err != nil {
		return nil, err
	}
	from := uint32(1)
	to := mbox.Messages
	if e.Limit > 0 && to > uint32(e.Limit) {
		from = to - uint32(e.Limit-1)
	}
	seqset := new(imap.SeqSet)
	seqset.AddRange(from, to)
	emails := make(chan *mail.Message)
	messages := make(chan *imap.Message)
	go func() {
		defer c.Logout()
		section := &imap.BodySectionName{}
		if err := c.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope, section.FetchItem()}, messages); err != nil {
			log.Println("error fetching:", err)
		}
	}()
	go func() {
		defer close(emails)
		for msg := range messages {
			for _, v := range msg.Body {
				m, err := mail.ReadMessage(v)
				if err != nil {
					log.Println(err)
				} else {
					emails <- m
				}
			}
		}
	}()
	return emails, nil
}

func (e *Emailer) ReadPOP3() (chan *mail.Message, error) {
	limit := e.Limit
	if limit < 1 {
		limit = 1000
	}
	emails := make(chan *mail.Message, limit)
	defer close(emails)

	log.Printf("pop3.DialTLS(%s)", e.POP3)
	c, err := pop3.DialTLS(e.POP3)
	if err != nil {
		return nil, err
	}
	defer c.Rset()

	log.Printf("c.Auth(%s, xyz)", e.From)
	if err := c.Auth(e.From, e.Password); err != nil {
		return nil, err
	}

	log.Printf("c.ListAll()")
	ids, _, err := c.ListAll()
	if err != nil {
		return nil, err
	}
	log.Printf("/c.ListAll() = %v", ids)

	for i := len(ids) - 1; i >= 0; i-- {
		id := ids[i]
		log.Printf("c.Retr(%v)", id)
		raw, err := c.Retr(id)
		if err != nil {
			return nil, err
		}
		msg, err := mail.ReadMessage(strings.NewReader(raw))
		if err != nil {
			return nil, err
		}
		select {
		case emails <- msg:
		default:
			return emails, nil
		}
	}
	return emails, nil
}

func (e *Emailer) Send(to, subj, msg string) error {
	headers := e.headers(to, e.From, subj)
	body := e.body(headers, msg)
	smtp, err := e.smtp()
	if err != nil {
		return err
	}
	defer smtp.Quit()
	if err := smtp.Mail(e.From); err != nil {
		return err
	}
	if err := smtp.Rcpt(to); err != nil {
		return err
	}
	w, err := smtp.Data()
	if err != nil {
		return err
	}
	if _, err := w.Write([]byte(body)); err != nil {
		return err
	}
	return w.Close()
}

func (e *Emailer) headers(to, from, subj string) map[string]string {
	return map[string]string{
		"To":      to,
		"From":    from,
		"Subject": subj,
	}
}

func (e *Emailer) body(headers map[string]string, body string) string {
	b := ""
	for k, v := range headers {
		b += fmt.Sprintf("%s: %s\r\n", k, v)
	}
	return b + "\r\n" + body
}

func (e *Emailer) smtp() (*smtp.Client, error) {
	host := strings.Split(e.SMTP, ":")[0]
	auth := smtp.PlainAuth("", e.From, e.Password, host)
	tlsconfig := &tls.Config{ServerName: host}
	conn, err := tls.Dial("tcp", e.SMTP, tlsconfig)
	if err != nil {
		return nil, fmt.Errorf("cannot dial %v: %v", e.SMTP, err)
	}
	smtp, err := smtp.NewClient(conn, host)
	if err != nil {
		return nil, fmt.Errorf("cannot new client %v: %v", host, err)
	}
	if err := smtp.Auth(auth); err != nil {
		return nil, fmt.Errorf("cannot auth client: %v", err)
	}
	return smtp, nil
}
