package jmap

import (
	"encoding/json"
	"io"
	"strings"
	"sync/atomic"

	"git.sr.ht/~rjarry/aerc/worker/types"
	"git.sr.ht/~rockorager/go-jmap"
	"git.sr.ht/~rockorager/go-jmap/mail"
	"git.sr.ht/~rockorager/go-jmap/mail/identity"
)

func (w *JMAPWorker) handleConnect(msg *types.Connect) error {
	w.client = &jmap.Client{SessionEndpoint: w.config.endpoint}

	if w.config.oauth {
		pass, _ := w.config.user.Password()
		w.client.WithAccessToken(pass)
	} else {
		user := w.config.user.Username()
		pass, _ := w.config.user.Password()
		w.client.WithBasicAuth(user, pass)
	}

	if session, err := w.cache.GetSession(); err == nil {
		w.client.Session = session
	}
	if w.client.Session == nil {
		if err := w.UpdateSession(); err != nil {
			return err
		}
	}

	go w.monitorChanges()

	return nil
}

func (w *JMAPWorker) AccountId() jmap.ID {
	switch {
	case w.client == nil:
		fallthrough
	case w.client.Session == nil:
		fallthrough
	case w.client.Session.PrimaryAccounts == nil:
		return ""
	default:
		return w.client.Session.PrimaryAccounts[mail.URI]
	}
}

func (w *JMAPWorker) UpdateSession() error {
	if err := w.client.Authenticate(); err != nil {
		return err
	}
	if err := w.cache.PutSession(w.client.Session); err != nil {
		w.w.Warnf("PutSession: %s", err)
	}
	return nil
}

func (w *JMAPWorker) GetIdentities() error {
	var req jmap.Request

	req.Invoke(&identity.Get{Account: w.AccountId()})
	resp, err := w.Do(&req)
	if err != nil {
		return err
	}
	for _, inv := range resp.Responses {
		switch r := inv.Args.(type) {
		case *identity.GetResponse:
			for _, ident := range r.List {
				w.identities[ident.Email] = ident
			}
		case *jmap.MethodError:
			return wrapMethodError(r)
		}
	}

	return nil
}

var seqnum uint64

func (w *JMAPWorker) Do(req *jmap.Request) (*jmap.Response, error) {
	seq := atomic.AddUint64(&seqnum, 1)
	body, _ := json.Marshal(req.Calls)
	w.w.Debugf(">%d> POST %s", seq, body)
	resp, err := w.client.Do(req)
	if err != nil {
		w.w.Debugf("<%d< %s", seq, err)
		// Try to update session in case an endpoint changed
		err := w.UpdateSession()
		if err != nil {
			return nil, err
		}
		// And try again if we succeeded
		resp, err = w.client.Do(req)
		if err != nil {
			return nil, err
		}
	}
	if resp.SessionState != w.client.Session.State {
		if err := w.UpdateSession(); err != nil {
			return nil, err
		}
	}
	w.w.Debugf("<%d< done", seq)
	return resp, err
}

func (w *JMAPWorker) Download(blobID jmap.ID) (io.ReadCloser, error) {
	seq := atomic.AddUint64(&seqnum, 1)
	replacer := strings.NewReplacer(
		"{accountId}", string(w.AccountId()),
		"{blobId}", string(blobID),
		"{type}", "application/octet-stream",
		"{name}", "filename",
	)
	url := replacer.Replace(w.client.Session.DownloadURL)
	w.w.Debugf(">%d> GET %s", seq, url)
	rd, err := w.client.Download(w.AccountId(), blobID)
	if err == nil {
		w.w.Debugf("<%d< 200 OK", seq)
	} else {
		w.w.Debugf("<%d< %s", seq, err)
	}
	return rd, err
}

func (w *JMAPWorker) Upload(reader io.Reader) (*jmap.UploadResponse, error) {
	seq := atomic.AddUint64(&seqnum, 1)
	url := strings.ReplaceAll(w.client.Session.UploadURL,
		"{accountId}", string(w.AccountId()))
	w.w.Debugf(">%d> POST %s", seq, url)
	resp, err := w.client.Upload(w.AccountId(), reader)
	if err == nil {
		w.w.Debugf("<%d< 200 OK", seq)
	} else {
		w.w.Debugf("<%d< %s", seq, err)
	}
	return resp, err
}
