package server_test

import (
	"bytes"
	"encoding/json"
	"os"
	"strconv"
	"strings"
	"testing"

	"github.com/keys-pub/keys"
	"github.com/keys-pub/keys-ext/http/api"
	"github.com/keys-pub/keys/dstore"
	"github.com/keys-pub/keys/http"
	"github.com/keys-pub/keys/tsutil"
	"github.com/stretchr/testify/require"
)

func TestMessages(t *testing.T) {
	env := newEnv(t)
	// env.logLevel = server.DebugLevel
	testMessages(t, env, testKeysSeeded())
}

func testMessages(t *testing.T, env *env, tk testKeys) {
	// keys.SetLogger(keys.NewLogger(keys.DebugLevel))

	srv := newTestServer(t, env)
	clock := env.clock

	channel := tk.channel

	// GET /channel/:cid/msgs (not found)
	req, err := http.NewAuthRequest("GET", dstore.Path("channel", channel.ID(), "msgs"), nil, "", clock.Now(), channel)
	require.NoError(t, err)
	code, _, body := srv.Serve(req)
	require.Equal(t, `{"error":{"code":404,"message":"kex1fzlrdfy4wlyaturcqkfq92ywj7lft9awtdg70d2yftzhspmc45qsvghhep not found"}}`, body)
	require.Equal(t, http.StatusNotFound, code)

	// POST /channel/:cid/msgs (not found)
	content := []byte("test1")
	contentHash := http.ContentHash(content)
	req, err = http.NewAuthRequest("POST", dstore.Path("channel", channel.ID(), "msgs"), bytes.NewReader(content), contentHash, clock.Now(), channel)
	require.NoError(t, err)
	code, _, body = srv.Serve(req)
	require.Equal(t, `{"error":{"code":404,"message":"kex1fzlrdfy4wlyaturcqkfq92ywj7lft9awtdg70d2yftzhspmc45qsvghhep not found"}}`, body)
	require.Equal(t, http.StatusNotFound, code)

	// PUT /channel/:cid
	req, err = http.NewAuthRequest("PUT", dstore.Path("channel", channel.ID()), nil, "", clock.Now(), channel)
	require.NoError(t, err)
	code, _, body = srv.Serve(req)
	require.Equal(t, http.StatusOK, code)
	var createResp api.ChannelCreateResponse
	testJSONUnmarshal(t, []byte(body), &createResp)
	require.Equal(t, channel.ID(), createResp.Channel.ID)
	require.NotEmpty(t, createResp.Channel.Token)

	// GET /channel/:cid/msgs
	req, err = http.NewAuthRequest("GET", dstore.Path("channel", channel.ID(), "msgs"), nil, "", clock.Now(), channel)
	require.NoError(t, err)
	code, _, body = srv.Serve(req)
	var messagesResp api.Events
	testJSONUnmarshal(t, []byte(body), &messagesResp)
	require.Empty(t, messagesResp.Events)
	require.Empty(t, messagesResp.Index)
	require.Equal(t, http.StatusOK, code)

	// POST /channel/:cid/msgs (no body)
	req, err = http.NewAuthRequest("POST", dstore.Path("channel", channel.ID(), "msgs"), nil, "", clock.Now(), channel)
	require.NoError(t, err)
	code, _, body = srv.Serve(req)
	expected := `{"error":{"code":400,"message":"missing body"}}`
	require.Equal(t, expected, body)
	require.Equal(t, http.StatusBadRequest, code)

	// POST /channel/:cid/msgs
	content = []byte("test1")
	contentHash = http.ContentHash(content)
	req, err = http.NewAuthRequest("POST", dstore.Path("channel", channel.ID(), "msgs"), bytes.NewReader(content), contentHash, clock.Now(), channel)
	require.NoError(t, err)
	code, _, body = srv.Serve(req)
	require.Equal(t, http.StatusOK, code)
	require.Equal(t, `{}`, body)
	content2 := []byte("test2")
	contentHash2 := http.ContentHash(content2)
	req, err = http.NewAuthRequest("POST", dstore.Path("channel", channel.ID(), "msgs"), bytes.NewReader(content2), contentHash2, clock.Now(), channel)
	require.NoError(t, err)
	code, _, body = srv.Serve(req)
	require.Equal(t, http.StatusOK, code)
	require.Equal(t, `{}`, body)

	// PUT /channel/:cid/msgs (invalid method)
	req, err = http.NewAuthRequest("PUT", dstore.Path("channel", channel.ID(), "msgs"), bytes.NewReader(content), contentHash, clock.Now(), channel)
	require.NoError(t, err)
	code, _, body = srv.Serve(req)
	require.Equal(t, http.StatusMethodNotAllowed, code)
	require.Equal(t, `{"error":{"code":405,"message":"method not allowed"}}`, body)

	// GET /channel/:cid/msgs
	req, err = http.NewAuthRequest("GET", dstore.Path("channel", channel.ID(), "msgs")+"?limit=1", nil, "", clock.Now(), channel)
	require.NoError(t, err)
	code, _, body = srv.Serve(req)
	require.Equal(t, http.StatusOK, code)
	var resp api.Events
	err = json.Unmarshal([]byte(body), &resp)
	require.NoError(t, err)
	require.Equal(t, int64(1), resp.Index)
	require.Equal(t, 1, len(resp.Events))
	require.Equal(t, []byte("test1"), resp.Events[0].Data)

	// GET /channel/:cid/msgs?idx=next
	req, err = http.NewAuthRequest("GET", dstore.Path("channel", channel.ID(), "msgs")+"?idx="+strconv.Itoa(int(resp.Index)), nil, "", clock.Now(), channel)
	require.NoError(t, err)
	code, _, body = srv.Serve(req)
	require.Equal(t, http.StatusOK, code)
	var resp2 api.Events
	err = json.Unmarshal([]byte(body), &resp2)
	require.NoError(t, err)
	require.Equal(t, int64(2), resp2.Index)
	require.Equal(t, 1, len(resp2.Events))
	require.Equal(t, []byte("test2"), resp2.Events[0].Data)

	// POST /channel/:cid/msgs
	content3 := []byte("test3")
	contentHash3 := http.ContentHash(content3)
	req, err = http.NewAuthRequest("POST", dstore.Path("channel", channel.ID(), "msgs"), bytes.NewReader(content3), contentHash3, clock.Now(), channel)
	require.NoError(t, err)
	code, _, _ = srv.Serve(req)
	require.Equal(t, http.StatusOK, code)

	// GET /channel/:cid/msgs
	req, err = http.NewAuthRequest("GET", dstore.Path("channel", channel.ID(), "msgs"), nil, "", clock.Now(), channel)
	require.NoError(t, err)
	code, _, body = srv.Serve(req)
	require.Equal(t, http.StatusOK, code)
	var resp3 api.EventsResponse
	err = json.Unmarshal([]byte(body), &resp3)
	require.NoError(t, err)
	require.Equal(t, 3, len(resp3.Events))
	require.Equal(t, []byte("test1"), resp3.Events[0].Data)
	require.Equal(t, []byte("test2"), resp3.Events[1].Data)
	require.Equal(t, []byte("test3"), resp3.Events[2].Data)

	// GET /channel/:cid/msgs (descending, limit=2)
	req, err = http.NewAuthRequest("GET", dstore.Path("channel", channel.ID(), "msgs")+"?order=desc&limit=2", nil, "", clock.Now(), channel)
	require.NoError(t, err)
	code, _, body = srv.Serve(req)
	require.Equal(t, http.StatusOK, code)
	var resp4 api.EventsResponse
	err = json.Unmarshal([]byte(body), &resp4)
	require.NoError(t, err)
	require.Equal(t, 2, len(resp4.Events))
	require.Equal(t, []byte("test3"), resp4.Events[0].Data)
	require.Equal(t, []byte("test2"), resp4.Events[1].Data)

	// POST /channel/:cid/msgs (message too large)
	large := bytes.Repeat([]byte{0x01}, 65*1024)
	largeHash := http.ContentHash(large)
	req, err = http.NewAuthRequest("POST", dstore.Path("channel", channel.ID(), "msgs"), bytes.NewReader(large), largeHash, clock.Now(), channel)
	require.NoError(t, err)
	code, _, body = srv.Serve(req)
	require.Equal(t, http.StatusRequestEntityTooLarge, code)
	require.Equal(t, `{"error":{"code":413,"message":"request too large"}}`, body)
}

func TestMessagesAuth(t *testing.T) {
	// SetContextLogger(NewContextLogger(DebugLevel))
	env := newEnv(t)
	srv := newTestServer(t, env)
	clock := env.clock

	tk := testKeysSeeded()
	channel := tk.channel

	// PUT /channel/:cid
	req, err := http.NewAuthRequest("PUT", dstore.Path("channel", channel.ID()), nil, "", clock.Now(), channel)
	require.NoError(t, err)
	code, _, body := srv.Serve(req)
	require.Equal(t, http.StatusOK, code)
	var channelCreate api.ChannelCreateResponse
	testJSONUnmarshal(t, []byte(body), &channelCreate)
	require.Equal(t, channel.ID(), channelCreate.Channel.ID)
	require.NotEmpty(t, channelCreate.Channel.Token)

	// GET /channel/:cid/msgs
	req, err = http.NewAuthRequest("GET", dstore.Path("channel", channel.ID(), "msgs"), nil, "", clock.Now(), channel)
	require.NoError(t, err)
	code, _, body = srv.Serve(req)
	var messagesResp api.Events
	testJSONUnmarshal(t, []byte(body), &messagesResp)
	require.Empty(t, messagesResp.Events)
	require.Empty(t, messagesResp.Index)
	require.Equal(t, http.StatusOK, code)

	// GET /channel/:cid/msgs (no auth)
	req, err = http.NewRequest("GET", dstore.Path("channel", channel.ID(), "msgs"), nil)
	require.NoError(t, err)
	code, _, body = srv.Serve(req)
	require.Equal(t, http.StatusForbidden, code)
	require.Equal(t, `{"error":{"code":403,"message":"auth failed"}}`, body)

	// GET /channel/:cid/msgs
	req, err = http.NewAuthRequest("GET", dstore.Path("channel", channel.ID(), "msgs"), nil, "", clock.Now(), channel)
	require.NoError(t, err)
	code, _, body = srv.Serve(req)
	require.Equal(t, http.StatusOK, code)
	testJSONUnmarshal(t, []byte(body), &messagesResp)
	require.Empty(t, messagesResp.Events)
	require.Empty(t, messagesResp.Index)

	// Replay last request
	reqReplay, err := http.NewRequest("GET", req.URL.String(), nil)
	reqReplay.Header.Set("Authorization", req.Header.Get("Authorization"))
	require.NoError(t, err)
	code, _, body = srv.Serve(reqReplay)
	require.Equal(t, http.StatusForbidden, code)
	require.Equal(t, `{"error":{"code":403,"message":"auth failed"}}`, body)

	// GET /channel/:cid/msgs (invalid authorization)
	authHeader := req.Header.Get("Authorization")
	randKey := keys.GenerateEdX25519Key()
	sig := strings.Split(authHeader, ":")[1]
	req, err = http.NewAuthRequest("GET", dstore.Path("channel", channel.ID(), "msgs"), nil, "", clock.Now(), randKey)
	require.NoError(t, err)
	req.Header.Set("Authorization", randKey.ID().String()+":"+sig)
	code, _, body = srv.Serve(req)
	require.Equal(t, http.StatusForbidden, code)
	require.Equal(t, `{"error":{"code":403,"message":"auth failed"}}`, body)

	req, err = http.NewAuthRequest("GET", dstore.Path("channel", channel.ID(), "msgs"), nil, "", clock.Now(), randKey)
	require.NoError(t, err)
	code, _, body = srv.Serve(req)
	require.Equal(t, http.StatusForbidden, code)
	require.Equal(t, `{"error":{"code":403,"message":"auth failed"}}`, body)

	// POST /channel/:cid/msgs (invalid authorization)
	msg := []byte("test")
	req, err = http.NewAuthRequest("POST", dstore.Path("channel", channel.ID(), "msgs"), bytes.NewReader(msg), http.ContentHash(msg), clock.Now(), randKey)
	require.NoError(t, err)
	req.Header.Set("Authorization", randKey.ID().String()+":"+sig)
	code, _, body = srv.Serve(req)
	require.Equal(t, http.StatusForbidden, code)
	require.Equal(t, `{"error":{"code":403,"message":"auth failed"}}`, body)

	req, err = http.NewAuthRequest("GET", dstore.Path("channel", channel.ID(), "msgs"), nil, "", clock.Now(), randKey)
	require.NoError(t, err)
	code, _, body = srv.Serve(req)
	require.Equal(t, http.StatusForbidden, code)
	require.Equal(t, `{"error":{"code":403,"message":"auth failed"}}`, body)
}

func TestMessagesFirestore(t *testing.T) {
	if os.Getenv("TEST_FIRESTORE") != "1" {
		t.Skip()
	}
	// firestore.SetContextLogger(firestore.NewContextLogger(firestore.DebugLevel))
	env := newEnvWithFire(t, testFirestore(t), tsutil.NewTestClock())
	// env.logLevel = server.DebugLevel
	testMessages(t, env, testKeysRandom())
}
