package imap

import (
	"encoding/json"
	"errors"
	"github.com/1f349/lotus/imap/marshal"
	"github.com/emersion/go-imap"
	"github.com/emersion/go-imap/client"
)

var imapStatusFlags = []imap.StatusItem{
	imap.StatusMessages,
	imap.StatusRecent,
	imap.StatusUidNext,
	imap.StatusUidValidity,
	imap.StatusUnseen,
}

type Client struct {
	ic *client.Client
}

var ErrInvalidArguments = errors.New("invalid arguments")

func (c *Client) HandleWS(action string, args json.RawMessage) (map[string]any, error) {
	switch action {
	case "copy":
		// TODO: implementation
	case "create":
		// TODO: implementation
	case "delete":
		// TODO: implementation
	case "list":
		var listArgs []string
		err := json.Unmarshal(args, &listArgs)
		if err != nil {
			return nil, err
		}

		if len(listArgs) != 2 {
			return nil, ErrInvalidArguments
		}

		// do list
		list, err := c.list(listArgs[0], listArgs[1])
		if err != nil {
			return nil, err
		}
		return map[string]any{"type": "list", "value": list}, nil
	case "fetch":
		var fetchArgs struct {
			Sync  uint64 `json:"sync"`
			Path  string `json:"path"`
			Start uint32 `json:"start"`
			End   uint32 `json:"end"`
			Limit uint32 `json:"limit"`
		}
		err := json.Unmarshal(args, &fetchArgs)
		if err != nil {
			return nil, err
		}

		if fetchArgs.Sync == 0 || len(fetchArgs.Path) == 0 || fetchArgs.Start == 0 || fetchArgs.End == 0 || fetchArgs.Limit == 0 {
			return nil, ErrInvalidArguments
		}

		// do fetch
		fetch, err := c.fetch(fetchArgs.Path, fetchArgs.Start, fetchArgs.End, fetchArgs.Limit)
		if err != nil {
			return nil, err
		}
		return map[string]any{"type": "fetch", "sync": fetchArgs.Sync, "value": marshal.MessageSliceJson(fetch)}, nil
	case "move":
		// TODO: implementation
	case "rename":
		// TODO: implementation
	case "search":
		// TODO: implementation
	case "status":
		// TODO: implementation
	}
	return map[string]any{"error": "Not implemented"}, nil
}

func (c *Client) fetch(folder string, start, end, limit uint32) ([]*imap.Message, error) {
	// select the mailbox
	mbox, err := c.ic.Select(folder, false)
	if err != nil {
		return nil, err
	}

	// setup fetch range
	if end > mbox.Messages {
		end = mbox.Messages
	}
	if end-start > limit {
		start = end - (limit - 1)
	}
	seqSet := new(imap.SeqSet)
	seqSet.AddRange(start, end)

	messages := make(chan *imap.Message, limit)
	done := make(chan error, 1)
	go func() {
		done <- c.ic.Fetch(seqSet, []imap.FetchItem{imap.FetchEnvelope, imap.FetchUid, imap.FetchFlags, imap.FetchInternalDate}, messages)
	}()

	out := make([]*imap.Message, 0, limit)
	for msg := range messages {
		out = append(out, msg)
	}
	if err := <-done; err != nil {
		return nil, err
	}
	return out, nil
}

func (c *Client) list(ref, name string) ([]*imap.MailboxInfo, error) {
	infos := make(chan *imap.MailboxInfo, 1)
	done := make(chan error, 1)
	go func() {
		done <- c.ic.List(ref, name, infos)
	}()

	out := make([]*imap.MailboxInfo, 0)
	for info := range infos {
		out = append(out, info)
	}
	if err := <-done; err != nil {
		return nil, err
	}
	return out, nil
}

func (c *Client) Move(seqset *imap.SeqSet, dest string) error {
	return c.ic.Move(seqset, dest)
}

func (c *Client) Noop() error {
	return c.ic.Noop()
}

func (c *Client) Rename(existingName, newName string) error {
	return c.ic.Rename(existingName, newName)
}

func (c *Client) Search(criteria *imap.SearchCriteria) ([]uint32, error) {
	return c.ic.Search(criteria)
}

func (c *Client) Status(name string) (*imap.MailboxStatus, error) {
	mbox, err := c.ic.Status(name, imapStatusFlags)
	return mbox, err
}