package services

import (
	"context"
	"crypto/tls"
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/emersion/go-imap"
	"github.com/emersion/go-imap/client"
	"github.com/emersion/go-message/mail"
	imapid "github.com/emersion/go-imap-id"

	"mengpost-backend/models"
)

// MailSummary 是列表页展示的邮件摘要.
type MailSummary struct {
	UID     uint32    `json:"uid"`
	Subject string    `json:"subject"`
	From    string    `json:"from"`
	Date    time.Time `json:"date"`
	Seen    bool      `json:"seen"`
	Mailbox string    `json:"mailbox"`
}

// MailDetail 邮件详情, 包含正文.
type MailDetail struct {
	MailSummary
	To   []string `json:"to"`
	Text string   `json:"text"`
	HTML string   `json:"html"`
}

func dial(acc *models.Account) (*client.Client, error) {
	addr := fmt.Sprintf("%s:%d", acc.IMAPHost, acc.IMAPPort)
	if acc.UseTLS {
		cfg := IMAPTLSConfig(acc)
		if cfg == nil {
			cfg = &tls.Config{MinVersion: tls.VersionTLS12, ServerName: acc.IMAPHost}
		}
		return client.DialTLS(addr, cfg)
	}
	return client.Dial(addr)
}

func imapAuthenticate(c *client.Client, acc *models.Account) error {
	if acc.AuthType == models.AuthTypeOAuthMicrosoft {
		if AppCfg == nil {
			return fmt.Errorf("服务未配置微软 OAuth")
		}
		mt, err := RefreshMicrosoftAccess(context.Background(), AppCfg, acc.OAuthRefreshToken)
		if err != nil {
			return err
		}
		if mt.RefreshToken != "" {
			_ = models.UpdateAccountOAuthRefresh(acc.ID, mt.RefreshToken)
		}
		if err := c.Authenticate(NewXOAUTH2Client(acc.Email, mt.AccessToken)); err != nil {
			return err
		}
	} else {
		if err := c.Login(acc.Email, acc.Password); err != nil {
			return err
		}
	}
	// Outlook/Exchange IMAP 在 AUTH 后对 RFC2971 ID 常返回 “Command Argument Error.12”；网易等仍需 ID。
	if !imapShouldSendClientID(acc) {
		return nil
	}
	return imapSendClientID(c)
}

func imapShouldSendClientID(acc *models.Account) bool {
	if acc.AuthType == models.AuthTypeOAuthMicrosoft {
		return false
	}
	if IsMicrosoftConsumerEmail(acc.Email) && strings.EqualFold(acc.IMAPHost, "outlook.office365.com") {
		return false
	}
	return true
}

// imapSendClientID 在 LOGIN 之后、SELECT/LIST 等之前发送 RFC2971 ID（网易 163/126/yeah 等要求，否则报 Unsafe Login）。
func imapSendClientID(c *client.Client) error {
	ic := imapid.NewClient(c)
	_, err := ic.ID(imapid.ID{
		imapid.FieldName:    "MengPost",
		imapid.FieldVersion: "1.0",
		imapid.FieldVendor:  "MengPost",
		imapid.FieldOS:      "Go",
	})
	return err
}

// ListMailboxes 列出账户下可用的邮件夹名称.
func ListMailboxes(acc *models.Account) ([]string, error) {
	c, err := dial(acc)
	if err != nil {
		return nil, err
	}
	defer c.Logout()
	if err := imapAuthenticate(c, acc); err != nil {
		return nil, err
	}
	ch := make(chan *imap.MailboxInfo, 20)
	done := make(chan error, 1)
	go func() { done <- c.List("", "*", ch) }()
	var out []string
	for m := range ch {
		out = append(out, m.Name)
	}
	if err := <-done; err != nil {
		return nil, err
	}
	return out, nil
}

// FetchMessages 获取指定邮件夹中最近 n 封邮件摘要.
func FetchMessages(acc *models.Account, mailbox string, limit int) ([]MailSummary, error) {
	if mailbox == "" {
		mailbox = "INBOX"
	}
	if limit <= 0 {
		limit = 30
	}
	c, err := dial(acc)
	if err != nil {
		return nil, err
	}
	defer c.Logout()
	if err := imapAuthenticate(c, acc); err != nil {
		return nil, err
	}
	mbox, err := c.Select(mailbox, true)
	if err != nil {
		return nil, err
	}
	if mbox.Messages == 0 {
		return []MailSummary{}, nil
	}
	from := uint32(1)
	if mbox.Messages > uint32(limit) {
		from = mbox.Messages - uint32(limit) + 1
	}
	seq := new(imap.SeqSet)
	seq.AddRange(from, mbox.Messages)
	items := []imap.FetchItem{imap.FetchEnvelope, imap.FetchFlags, imap.FetchUid}
	ch := make(chan *imap.Message, limit)
	done := make(chan error, 1)
	go func() { done <- c.Fetch(seq, items, ch) }()
	var out []MailSummary
	for msg := range ch {
		sum := MailSummary{UID: msg.Uid, Mailbox: mailbox}
		if msg.Envelope != nil {
			sum.Subject = msg.Envelope.Subject
			sum.Date = msg.Envelope.Date
			if len(msg.Envelope.From) > 0 {
				a := msg.Envelope.From[0]
				sum.From = fmt.Sprintf("%s <%s@%s>", a.PersonalName, a.MailboxName, a.HostName)
			}
		}
		for _, f := range msg.Flags {
			if f == imap.SeenFlag {
				sum.Seen = true
			}
		}
		out = append(out, sum)
	}
	if err := <-done; err != nil {
		return nil, err
	}
	// 最新的在前
	for i, j := 0, len(out)-1; i < j; i, j = i+1, j-1 {
		out[i], out[j] = out[j], out[i]
	}
	return out, nil
}

// FetchMessage 根据 UID 获取单封邮件完整内容.
func FetchMessage(acc *models.Account, mailbox string, uid uint32) (*MailDetail, error) {
	if mailbox == "" {
		mailbox = "INBOX"
	}
	c, err := dial(acc)
	if err != nil {
		return nil, err
	}
	defer c.Logout()
	if err := imapAuthenticate(c, acc); err != nil {
		return nil, err
	}
	if _, err := c.Select(mailbox, false); err != nil {
		return nil, err
	}
	seq := new(imap.SeqSet)
	seq.AddNum(uid)
	section := &imap.BodySectionName{}
	items := []imap.FetchItem{imap.FetchEnvelope, imap.FetchFlags, imap.FetchUid, section.FetchItem()}
	ch := make(chan *imap.Message, 1)
	done := make(chan error, 1)
	go func() { done <- c.UidFetch(seq, items, ch) }()
	msg := <-ch
	if err := <-done; err != nil {
		return nil, err
	}
	if msg == nil {
		return nil, fmt.Errorf("message not found")
	}
	d := &MailDetail{MailSummary: MailSummary{UID: msg.Uid, Mailbox: mailbox}}
	if msg.Envelope != nil {
		d.Subject = msg.Envelope.Subject
		d.Date = msg.Envelope.Date
		if len(msg.Envelope.From) > 0 {
			a := msg.Envelope.From[0]
			d.From = fmt.Sprintf("%s <%s@%s>", a.PersonalName, a.MailboxName, a.HostName)
		}
		for _, a := range msg.Envelope.To {
			d.To = append(d.To, fmt.Sprintf("%s@%s", a.MailboxName, a.HostName))
		}
	}
	for _, f := range msg.Flags {
		if f == imap.SeenFlag {
			d.Seen = true
		}
	}
	r := msg.GetBody(section)
	if r == nil {
		return d, nil
	}
	mr, err := mail.CreateReader(r)
	if err != nil {
		b, _ := io.ReadAll(r)
		d.Text = string(b)
		return d, nil
	}
	for {
		p, err := mr.NextPart()
		if err == io.EOF {
			break
		} else if err != nil {
			break
		}
		switch h := p.Header.(type) {
		case *mail.InlineHeader:
			ct, _, _ := h.ContentType()
			b, _ := io.ReadAll(p.Body)
			if strings.Contains(ct, "html") {
				d.HTML = string(b)
			} else {
				d.Text = string(b)
			}
		}
	}
	return d, nil
}
