package client

import (
	"context"
	"encoding/json"
	"net/url"
	"sort"

	"github.com/keys-pub/keys"
	"github.com/keys-pub/keys-ext/http/api"
	"github.com/keys-pub/keys/dstore"
	"github.com/pkg/errors"
)

// ChannelCreated ...
type ChannelCreated struct {
	Channel *api.Channel
	Message *api.Message
}

// ChannelCreate creates a channel.
func (c *Client) ChannelCreate(ctx context.Context, channel *keys.EdX25519Key, user *keys.EdX25519Key, info *api.ChannelInfo) (*ChannelCreated, error) {
	path := dstore.Path("channel", channel.ID())

	var msg *api.Message
	var body []byte
	if info != nil {
		msg = api.NewMessageForChannelInfo(user.ID(), info)
		msgEncrypted, err := msg.Encrypt(user, channel.ID())
		if err != nil {
			return nil, err
		}
		req := api.ChannelCreateRequest{
			Message: msgEncrypted,
		}
		b, err := json.Marshal(req)
		if err != nil {
			return nil, err
		}
		body = b
	}

	params := url.Values{}
	resp, err := c.req(ctx, request{Method: "PUT", Path: path, Params: params, Body: body, Key: channel})
	if err != nil {
		return nil, err
	}
	var out api.ChannelCreateResponse
	if err := json.Unmarshal(resp.Data, &out); err != nil {
		return nil, err
	}
	return &ChannelCreated{
		Channel: out.Channel,
		Message: msg,
	}, nil
}

// ChannelsStatus lists channel status.
func (c *Client) ChannelsStatus(ctx context.Context, channelTokens ...*api.ChannelToken) ([]*api.ChannelStatus, error) {
	statusReq := api.ChannelsStatusRequest{
		Channels: map[keys.ID]string{},
	}
	for _, ct := range channelTokens {
		statusReq.Channels[ct.Channel] = ct.Token
	}

	body, err := json.Marshal(statusReq)
	if err != nil {
		return nil, err
	}

	params := url.Values{}
	resp, err := c.req(ctx, request{Method: "POST", Path: "/channels/status", Params: params, Body: body})
	if err != nil {
		return nil, err
	}

	var out api.ChannelsStatusResponse
	if err := json.Unmarshal(resp.Data, &out); err != nil {
		return nil, err
	}
	sort.Slice(out.Channels, func(i, j int) bool {
		return out.Channels[i].Timestamp > out.Channels[j].Timestamp
	})
	return out.Channels, nil
}

// InviteToChannel sends a direct message containing a channel key.
func (c *Client) InviteToChannel(ctx context.Context, invite *api.ChannelInvite, sender *keys.EdX25519Key) error {
	if sender.ID() != invite.Sender {
		return errors.Errorf("invite sender mismatch")
	}
	msg := api.NewMessageForChannelInvites(invite.Sender, []*api.ChannelInvite{invite})
	if err := c.DirectMessageSend(ctx, msg, sender, invite.Recipient); err != nil {
		return err
	}
	return nil
}

// ChannelInvites ..
type ChannelInvites struct {
	Invites   []*api.ChannelInvite
	Index     int64
	Truncated bool
}

// ChannelInvites lists channel invites from directs.
func (c *Client) ChannelInvites(ctx context.Context, recipient *keys.EdX25519Key, opts *MessagesOpts) (*ChannelInvites, error) {
	directs, err := c.DirectMessages(ctx, recipient, opts)
	if err != nil {
		return nil, err
	}
	msgs, err := directs.Decrypt(recipient)
	if err != nil {
		return nil, err
	}
	invites := []*api.ChannelInvite{}
	for _, msg := range msgs {
		if msg.ChannelInvites != nil {
			invites = append(invites, msg.ChannelInvites...)
		}
	}
	return &ChannelInvites{
		Invites:   invites,
		Index:     directs.Index,
		Truncated: directs.Truncated,
	}, nil
}
