package server

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/keys-pub/keys"
	"github.com/labstack/echo/v4"
	"github.com/pkg/errors"
)

func discoKey(kid keys.ID, rid keys.ID, typ string) string {
	addr := kid.String()
	if kid != rid {
		a, err := keys.NewAddress(kid, rid)
		if err != nil {
			panic(err)
		}
		addr = a.String()
	}
	return fmt.Sprintf("d-%s-%s", addr, shortType(typ))
}

func (s *Server) putDisco(c echo.Context) error {
	s.logger.Infof("Server %s %s", c.Request().Method, c.Request().URL.String())
	ctx := c.Request().Context()

	if c.Request().Body == nil {
		return s.ErrBadRequest(c, errors.Errorf("missing body"))
	}

	b, err := ioutil.ReadAll(c.Request().Body)
	if err != nil {
		return s.ErrInternalServer(c, err)
	}

	if len(b) > 256 {
		// TODO: Check length before reading data
		return s.ErrBadRequest(c, errors.Errorf("message too large (greater than 256 bytes)"))
	}

	auth, err := s.auth(c, newAuth("Authorization", "kid", b))
	if err != nil {
		return s.ErrForbidden(c, err)
	}

	recipient := c.Param("rid")
	if recipient == "" {
		return s.ErrBadRequest(c, errors.Errorf("no recipient id specified"))
	}
	rid, err := keys.ParseID(recipient)
	if err != nil {
		return s.ErrBadRequest(c, err)
	}

	typ := c.Param("type")
	if typ == "" {
		return s.ErrBadRequest(c, errors.Errorf("no type"))
	}
	if typ != "offer" && typ != "answer" {
		return s.ErrBadRequest(c, errors.Errorf("invalid type"))
	}

	expire, err := queryParamDuration(c, "expire", time.Second*15)
	if err != nil {
		return s.ErrBadRequest(c, err)
	}

	if len(expire.String()) > 64 {
		return s.ErrBadRequest(c, errors.Errorf("invalid expire"))
	}
	if expire > time.Minute {
		return s.ErrBadRequest(c, errors.Errorf("max expire is 1m"))
	}

	key := discoKey(auth.KID, rid, typ)
	if err := s.rds.Set(ctx, key, string(b)); err != nil {
		return s.ErrInternalServer(c, err)
	}

	if err := s.rds.Expire(ctx, key, expire); err != nil {
		return s.ErrInternalServer(c, err)
	}

	var resp struct{}
	return JSON(c, http.StatusOK, resp)
}

func (s *Server) getDisco(c echo.Context) error {
	s.logger.Infof("Server %s %s", c.Request().Method, c.Request().URL.String())
	ctx := c.Request().Context()

	auth, err := s.auth(c, newAuth("Authorization", "rid", nil))
	if err != nil {
		return s.ErrForbidden(c, err)
	}

	sender := c.Param("kid")
	if sender == "" {
		return s.ErrBadRequest(c, errors.Errorf("no kid specified"))
	}
	kid, err := keys.ParseID(sender)
	if err != nil {
		return s.ErrBadRequest(c, err)
	}

	typ := c.Param("type")
	if typ == "" {
		return s.ErrBadRequest(c, errors.Errorf("no type"))
	}
	if typ != "offer" && typ != "answer" {
		return s.ErrBadRequest(c, errors.Errorf("invalid type"))
	}

	key := discoKey(kid, auth.KID, typ)
	out, err := s.rds.Get(ctx, key)
	if err != nil {
		return s.ErrInternalServer(c, err)
	}
	if out == "" {
		return s.ErrNotFound(c, nil)
	}
	// Delete after get
	if err := s.rds.Delete(ctx, key); err != nil {
		return s.ErrInternalServer(c, err)
	}
	return c.Blob(http.StatusOK, echo.MIMEOctetStream, []byte(out))
}

func (s *Server) deleteDisco(c echo.Context) error {
	ctx := c.Request().Context()
	s.logger.Infof("Server %s %s", c.Request().Method, c.Request().URL.String())

	auth, err := s.auth(c, newAuth("Authorization", "kid", nil))
	if err != nil {
		return s.ErrForbidden(c, err)
	}

	recipient := c.Param("rid")
	if recipient == "" {
		return s.ErrBadRequest(c, errors.Errorf("no recipient id specified"))
	}
	rid, err := keys.ParseID(recipient)
	if err != nil {
		return s.ErrBadRequest(c, err)
	}

	okey := discoKey(auth.KID, rid, "offer")
	if err := s.rds.Delete(ctx, okey); err != nil {
		return s.ErrInternalServer(c, err)
	}
	akey := discoKey(auth.KID, rid, "answer")
	if err := s.rds.Delete(ctx, akey); err != nil {
		return s.ErrInternalServer(c, err)
	}

	var resp struct{}
	return JSON(c, http.StatusOK, resp)
}

func shortType(s string) string {
	switch s {
	case "offer":
		return "o"
	case "answer":
		return "a"
	}
	panic(errors.Errorf("invalid type"))
}
