package imapclient_test

import (
	"io"
	"log"
	"time"

	"github.com/emersion/go-message/mail"
	"github.com/emersion/go-sasl"

	"github.com/emersion/go-imap/v2"
	"github.com/emersion/go-imap/v2/imapclient"
)

func ExampleClient() {
	c, err := imapclient.DialTLS("mail.example.org:993", nil)
	if err != nil {
		log.Fatalf("failed to dial IMAP server: %v", err)
	}
	defer c.Close()

	if err := c.Login("root", "asdf").Wait(); err != nil {
		log.Fatalf("failed to login: %v", err)
	}

	mailboxes, err := c.List("", "%", nil).Collect()
	if err != nil {
		log.Fatalf("failed to list mailboxes: %v", err)
	}
	log.Printf("Found %v mailboxes", len(mailboxes))
	for _, mbox := range mailboxes {
		log.Printf(" - %v", mbox.Mailbox)
	}

	selectedMbox, err := c.Select("INBOX", nil).Wait()
	if err != nil {
		log.Fatalf("failed to select INBOX: %v", err)
	}
	log.Printf("INBOX contains %v messages", selectedMbox.NumMessages)

	if selectedMbox.NumMessages > 0 {
		seqSet := imap.SeqSetNum(1)
		fetchOptions := &imap.FetchOptions{Envelope: true}
		messages, err := c.Fetch(seqSet, fetchOptions).Collect()
		if err != nil {
			log.Fatalf("failed to fetch first message in INBOX: %v", err)
		}
		log.Printf("subject of first message in INBOX: %v", messages[0].Envelope.Subject)
	}

	if err := c.Logout().Wait(); err != nil {
		log.Fatalf("failed to logout: %v", err)
	}
}

func ExampleClient_pipelining() {
	var c *imapclient.Client

	uid := imap.UID(42)
	fetchOptions := &imap.FetchOptions{Envelope: true}

	// Login, select and fetch a message in a single roundtrip
	loginCmd := c.Login("root", "root")
	selectCmd := c.Select("INBOX", nil)
	fetchCmd := c.Fetch(imap.UIDSetNum(uid), fetchOptions)

	if err := loginCmd.Wait(); err != nil {
		log.Fatalf("failed to login: %v", err)
	}
	if _, err := selectCmd.Wait(); err != nil {
		log.Fatalf("failed to select INBOX: %v", err)
	}
	if messages, err := fetchCmd.Collect(); err != nil {
		log.Fatalf("failed to fetch message: %v", err)
	} else {
		log.Printf("Subject: %v", messages[0].Envelope.Subject)
	}
}

func ExampleClient_Append() {
	var c *imapclient.Client

	buf := []byte("From: <root@nsa.gov>\r\n\r\nHi <3")
	size := int64(len(buf))
	appendCmd := c.Append("INBOX", size, nil)
	if _, err := appendCmd.Write(buf); err != nil {
		log.Fatalf("failed to write message: %v", err)
	}
	if err := appendCmd.Close(); err != nil {
		log.Fatalf("failed to close message: %v", err)
	}
	if _, err := appendCmd.Wait(); err != nil {
		log.Fatalf("APPEND command failed: %v", err)
	}
}

func ExampleClient_Status() {
	var c *imapclient.Client

	options := imap.StatusOptions{NumMessages: true}
	if data, err := c.Status("INBOX", &options).Wait(); err != nil {
		log.Fatalf("STATUS command failed: %v", err)
	} else {
		log.Printf("INBOX contains %v messages", *data.NumMessages)
	}
}

func ExampleClient_List_stream() {
	var c *imapclient.Client

	// ReturnStatus requires server support for IMAP4rev2 or LIST-STATUS
	listCmd := c.List("", "%", &imap.ListOptions{
		ReturnStatus: &imap.StatusOptions{
			NumMessages: true,
			NumUnseen:   true,
		},
	})
	for {
		mbox := listCmd.Next()
		if mbox == nil {
			break
		}
		log.Printf("Mailbox %q contains %v messages (%v unseen)", mbox.Mailbox, *mbox.Status.NumMessages, *mbox.Status.NumUnseen)
	}
	if err := listCmd.Close(); err != nil {
		log.Fatalf("LIST command failed: %v", err)
	}
}

func ExampleClient_Store() {
	var c *imapclient.Client

	seqSet := imap.SeqSetNum(1)
	storeFlags := imap.StoreFlags{
		Op:     imap.StoreFlagsAdd,
		Flags:  []imap.Flag{imap.FlagFlagged},
		Silent: true,
	}
	if err := c.Store(seqSet, &storeFlags, nil).Close(); err != nil {
		log.Fatalf("STORE command failed: %v", err)
	}
}

func ExampleClient_Fetch() {
	var c *imapclient.Client

	seqSet := imap.SeqSetNum(1)
	bodySection := &imap.FetchItemBodySection{Specifier: imap.PartSpecifierHeader}
	fetchOptions := &imap.FetchOptions{
		Flags:       true,
		Envelope:    true,
		BodySection: []*imap.FetchItemBodySection{bodySection},
	}
	messages, err := c.Fetch(seqSet, fetchOptions).Collect()
	if err != nil {
		log.Fatalf("FETCH command failed: %v", err)
	}

	msg := messages[0]
	header := msg.FindBodySection(bodySection)

	log.Printf("Flags: %v", msg.Flags)
	log.Printf("Subject: %v", msg.Envelope.Subject)
	log.Printf("Header:\n%v", string(header))
}

func ExampleClient_Fetch_streamBody() {
	var c *imapclient.Client

	seqSet := imap.SeqSetNum(1)
	bodySection := &imap.FetchItemBodySection{}
	fetchOptions := &imap.FetchOptions{
		UID:         true,
		BodySection: []*imap.FetchItemBodySection{bodySection},
	}
	fetchCmd := c.Fetch(seqSet, fetchOptions)
	defer fetchCmd.Close()

	for {
		msg := fetchCmd.Next()
		if msg == nil {
			break
		}

		for {
			item := msg.Next()
			if item == nil {
				break
			}

			switch item := item.(type) {
			case imapclient.FetchItemDataUID:
				log.Printf("UID: %v", item.UID)
			case imapclient.FetchItemDataBodySection:
				b, err := io.ReadAll(item.Literal)
				if err != nil {
					log.Fatalf("failed to read body section: %v", err)
				}
				log.Printf("Body:\n%v", string(b))
			}
		}
	}

	if err := fetchCmd.Close(); err != nil {
		log.Fatalf("FETCH command failed: %v", err)
	}
}

func ExampleClient_Fetch_parseBody() {
	var c *imapclient.Client

	// Send a FETCH command to fetch the message body
	seqSet := imap.SeqSetNum(1)
	bodySection := &imap.FetchItemBodySection{}
	fetchOptions := &imap.FetchOptions{
		BodySection: []*imap.FetchItemBodySection{bodySection},
	}
	fetchCmd := c.Fetch(seqSet, fetchOptions)
	defer fetchCmd.Close()

	msg := fetchCmd.Next()
	if msg == nil {
		log.Fatalf("FETCH command did not return any message")
	}

	// Find the body section in the response
	var bodySectionData imapclient.FetchItemDataBodySection
	ok := false
	for {
		item := msg.Next()
		if item == nil {
			break
		}
		bodySectionData, ok = item.(imapclient.FetchItemDataBodySection)
		if ok {
			break
		}
	}
	if !ok {
		log.Fatalf("FETCH command did not return body section")
	}

	// Read the message via the go-message library
	mr, err := mail.CreateReader(bodySectionData.Literal)
	if err != nil {
		log.Fatalf("failed to create mail reader: %v", err)
	}

	// Print a few header fields
	h := mr.Header
	if date, err := h.Date(); err != nil {
		log.Printf("failed to parse Date header field: %v", err)
	} else {
		log.Printf("Date: %v", date)
	}
	if to, err := h.AddressList("To"); err != nil {
		log.Printf("failed to parse To header field: %v", err)
	} else {
		log.Printf("To: %v", to)
	}
	if subject, err := h.Text("Subject"); err != nil {
		log.Printf("failed to parse Subject header field: %v", err)
	} else {
		log.Printf("Subject: %v", subject)
	}

	// Process the message's parts
	for {
		p, err := mr.NextPart()
		if err == io.EOF {
			break
		} else if err != nil {
			log.Fatalf("failed to read message part: %v", err)
		}

		switch h := p.Header.(type) {
		case *mail.InlineHeader:
			// This is the message's text (can be plain-text or HTML)
			b, _ := io.ReadAll(p.Body)
			log.Printf("Inline text: %v", string(b))
		case *mail.AttachmentHeader:
			// This is an attachment
			filename, _ := h.Filename()
			log.Printf("Attachment: %v", filename)
		}
	}

	if err := fetchCmd.Close(); err != nil {
		log.Fatalf("FETCH command failed: %v", err)
	}
}

func ExampleClient_Search() {
	var c *imapclient.Client

	data, err := c.UIDSearch(&imap.SearchCriteria{
		Body: []string{"Hello world"},
	}, nil).Wait()
	if err != nil {
		log.Fatalf("UID SEARCH command failed: %v", err)
	}
	log.Fatalf("UIDs matching the search criteria: %v", data.AllUIDs())
}

func ExampleClient_Idle() {
	options := imapclient.Options{
		UnilateralDataHandler: &imapclient.UnilateralDataHandler{
			Expunge: func(seqNum uint32) {
				log.Printf("message %v has been expunged", seqNum)
			},
			Mailbox: func(data *imapclient.UnilateralDataMailbox) {
				if data.NumMessages != nil {
					log.Printf("a new message has been received")
				}
			},
		},
	}

	c, err := imapclient.DialTLS("mail.example.org:993", &options)
	if err != nil {
		log.Fatalf("failed to dial IMAP server: %v", err)
	}
	defer c.Close()

	if err := c.Login("root", "asdf").Wait(); err != nil {
		log.Fatalf("failed to login: %v", err)
	}
	if _, err := c.Select("INBOX", nil).Wait(); err != nil {
		log.Fatalf("failed to select INBOX: %v", err)
	}

	// Start idling
	idleCmd, err := c.Idle()
	if err != nil {
		log.Fatalf("IDLE command failed: %v", err)
	}
	defer idleCmd.Close()

	done := make(chan error, 1)
	go func() {
		done <- idleCmd.Wait()
	}()

	// Wait for 30s to receive updates from the server, then stop idling
	t := time.NewTimer(30 * time.Second)
	defer t.Stop()
	select {
	case <-t.C:
		if err := idleCmd.Close(); err != nil {
			log.Fatalf("failed to stop idling: %v", err)
		}
		if err := <-done; err != nil {
			log.Fatalf("IDLE command failed: %v", err)
		}
	case err := <-done:
		if err != nil {
			log.Fatalf("IDLE command failed: %v", err)
		}
	}
}

func ExampleClient_Authenticate_oauth() {
	var (
		c        *imapclient.Client
		username string
		token    string
	)

	if !c.Caps().Has(imap.AuthCap(sasl.OAuthBearer)) {
		log.Fatal("OAUTHBEARER not supported by the server")
	}

	saslClient := sasl.NewOAuthBearerClient(&sasl.OAuthBearerOptions{
		Username: username,
		Token:    token,
	})
	if err := c.Authenticate(saslClient); err != nil {
		log.Fatalf("authentication failed: %v", err)
	}
}

func ExampleClient_Closed() {
	c, err := imapclient.DialTLS("mail.example.org:993", nil)
	if err != nil {
		log.Fatalf("failed to dial IMAP server: %v", err)
	}

	selected := false

	go func(c *imapclient.Client) {
		if err := c.Login("root", "asdf").Wait(); err != nil {
			log.Fatalf("failed to login: %v", err)
		}

		if _, err := c.Select("INBOX", nil).Wait(); err != nil {
			log.Fatalf("failed to select INBOX: %v", err)
		}

		selected = true

		c.Close()
	}(c)

	// This channel shall be closed when the connection is closed.
	<-c.Closed()
	log.Println("Connection has been closed")

	if !selected {
		log.Fatalf("Connection was closed before selecting mailbox")
	}
}
