package cmd

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"sync"
	"testing"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/umputun/go-flags"

	"github.com/umputun/remark42/backend/app/store"
)

type cleanedComments struct {
	ids  []string
	lock sync.Mutex
}

func TestCleanup_IsSpam(t *testing.T) {
	cc := CleanupCommand{
		BadWords: []string{"bad1", "bad2", "very bad", "xyz"},
		BadUsers: []string{"bu_"},
	}

	tbl := []struct {
		text      string
		user      string
		score     int
		isSpam    bool
		spamScore float64
		name      string
	}{
		{"", "", 1, false, 0, "empty passes"},
		{"one very bad two blah bad1 bad2 http://xyz.com", "bu_user", 0, true, 90, "3badwords link 0score baduser"},
		{"one very bad two blah bad1 bad2", "bu_user", 0, true, 67.5, "3 bad words 1score baduser"},
		{"bad1 bad2 xyz very bad", "bu_user", 0, true, 80, "4badwords 0score baduser"},
		{"bad1 bad2 xyz very bad", "user", 0, true, 70, "4badwords 0score"},
		{"bad1 bad2 xyz very bad", "user", 1, false, 50, "4badwords 1score"},
		{"bad1 test 12345", "user", 0, false, 32.5, "1badwords 0score"},
	}

	for n, tt := range tbl {
		tt := tt
		checkName := fmt.Sprintf("check-%d-%s", n, tt.name)
		t.Run(checkName, func(t *testing.T) {
			c := store.Comment{ID: checkName, Text: tt.text, Score: tt.score}
			c.User.ID = tt.user
			r, score := cc.isSpam(c)
			assert.Equal(t, tt.isSpam, r)
			assert.InDelta(t, tt.spamScore, score, 0.01)
		})
	}
}

func TestCleanup_postsInRange(t *testing.T) {

	r := chi.NewRouter()
	cleanupRoutes(t, r, nil)
	ts := httptest.NewServer(r)
	defer ts.Close()

	cmd := CleanupCommand{}
	cmd.SetCommon(CommonOpts{RemarkURL: ts.URL, SharedSecret: "123456"})
	p := flags.NewParser(&cmd, flags.Default)
	_, err := p.ParseArgs([]string{"--site=remark", "--bword=bad1", "--bword=bad2", "--buser=bu_", "--admin-passwd=secret"})
	require.NoError(t, err)
	posts, err := cmd.postsInRange("20181218", "20181219")
	assert.NoError(t, err)
	assert.Equal(t, 2, len(posts))

	posts, err = cmd.postsInRange("", "")
	assert.NoError(t, err)
	assert.Equal(t, 3, len(posts))

	_, err = cmd.postsInRange("xxx", "yyy")
	assert.Error(t, err)
}

func TestCleanup_listComments(t *testing.T) {
	r := chi.NewRouter()
	cleanupRoutes(t, r, nil)
	ts := httptest.NewServer(r)
	defer ts.Close()

	cmd := CleanupCommand{}
	cmd.SetCommon(CommonOpts{RemarkURL: ts.URL, SharedSecret: "123456"})
	p := flags.NewParser(&cmd, flags.Default)
	_, err := p.ParseArgs([]string{"--site=remark", "--bword=bad1", "--bword=bad2", "--buser=bu_", "--admin-passwd=secret"})
	require.NoError(t, err)

	comments, err := cmd.listComments("http://test.com/post1")
	assert.NoError(t, err)
	assert.Equal(t, 3, len(comments))

	comments, err = cmd.listComments("http://test.com/post2")
	assert.NoError(t, err)
	assert.Equal(t, 1, len(comments))

	comments, err = cmd.listComments("http://test.com/post-bad")
	assert.NoError(t, err)
	assert.Equal(t, 0, len(comments))
}

func TestCleanup_ExecuteSpam(t *testing.T) {
	cleaned := cleanedComments{}
	r := chi.NewRouter()
	cleanupRoutes(t, r, &cleaned)
	ts := httptest.NewServer(r)
	defer ts.Close()

	cmd := CleanupCommand{}
	cmd.SetCommon(CommonOpts{RemarkURL: ts.URL, SharedSecret: "123456"})
	p := flags.NewParser(&cmd, flags.Default)
	_, err := p.ParseArgs([]string{"--site=remark", "--bword=bad1", "--bword=bad2", "--buser=bu_",
		"--from=20181217", "--to=20181218", "--admin-passwd=secret"})
	require.NoError(t, err)
	err = cmd.Execute(nil)
	assert.NoError(t, err)
	t.Logf("deleted %+v", cleaned.ids)
	assert.Equal(t, []string{"/api/v1/admin/comment/1", "/api/v1/admin/comment/3", "/api/v1/admin/comment/11"}, cleaned.ids)
}

func TestCleanup_ExecuteTitle(t *testing.T) {
	titledComments := cleanedComments{}
	r := chi.NewRouter()
	cleanupRoutes(t, r, &titledComments)
	ts := httptest.NewServer(r)
	defer ts.Close()

	cmd := CleanupCommand{}
	cmd.SetCommon(CommonOpts{RemarkURL: ts.URL, SharedSecret: "123456"})
	p := flags.NewParser(&cmd, flags.Default)
	_, err := p.ParseArgs([]string{"--site=remark", "--title", "--from=20181217", "--to=20181218", "--admin-passwd=secret"})
	require.NoError(t, err)
	err = cmd.Execute(nil)
	assert.NoError(t, err)
	t.Logf("set titles for %+v", titledComments.ids)
	assert.Equal(t, []string{"/api/v1/admin/title/1", "/api/v1/admin/title/2", "/api/v1/admin/title/3", "/api/v1/admin/title/11"}, titledComments.ids)
}

func cleanupRoutes(t *testing.T, r *chi.Mux, c *cleanedComments) {
	r.HandleFunc("/api/v1/list", func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, "GET", r.Method)
		require.Equal(t, "site=remark&limit=10000", r.URL.RawQuery)
		list := []store.PostInfo{
			{
				URL:     "http://test.com/post1",
				FirstTS: time.Date(2018, 12, 17, 10, 0, 0, 0, time.Local),
				LastTS:  time.Date(2018, 12, 17, 10, 30, 0, 0, time.Local),
			},
			{
				URL:     "http://test.com/post2",
				FirstTS: time.Date(2018, 12, 18, 10, 0, 0, 0, time.Local),
				LastTS:  time.Date(2018, 12, 18, 10, 30, 0, 0, time.Local),
			},
			{
				URL:     "http://test.com/post3",
				FirstTS: time.Date(2018, 12, 19, 10, 0, 0, 0, time.Local),
				LastTS:  time.Date(2018, 12, 19, 10, 30, 0, 0, time.Local),
			},
		}
		require.NoError(t, json.NewEncoder(w).Encode(list))
	})

	r.HandleFunc("/api/v1/find", func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, "GET", r.Method)
		require.Equal(t, "remark", r.URL.Query().Get("site"))
		require.Equal(t, "plain", r.URL.Query().Get("format"))

		commentsWithInfo := struct {
			Comments []store.Comment `json:"comments"`
			Info     store.PostInfo  `json:"info,omitempty"`
		}{}

		switch r.URL.Query().Get("url") {
		case "http://test.com/post1":
			commentsWithInfo.Comments = []store.Comment{
				{ID: "1", Text: "one very bad two blah bad1 bad2 http://xyz.com", Score: 0, User: store.User{ID: "bu_user"}},
				{ID: "2", Text: "good one http://xyz.com", Score: 1, User: store.User{ID: "bu_user"}},
				{ID: "3", Text: "http://xyz.com bad1 bad2", Score: 0, User: store.User{ID: "user"}},
			}
		case "http://test.com/post2":
			commentsWithInfo.Comments = []store.Comment{
				{ID: "11", Text: "one very bad two blah bad1 bad2 http://xyz.com", Score: 0, User: store.User{ID: "bu_user"}},
			}
		case "http://test.com/post3":
			commentsWithInfo.Comments = []store.Comment{}
		}

		require.NoError(t, json.NewEncoder(w).Encode(commentsWithInfo))
	})

	r.HandleFunc("/api/v1/admin/comment/{id}", func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, "DELETE", r.Method)
		t.Log("delete ", r.URL.Path)
		c.lock.Lock()
		c.ids = append(c.ids, r.URL.Path)
		c.lock.Unlock()
	})

	r.HandleFunc("/api/v1/admin/title/{id}", func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, "PUT", r.Method)
		t.Log("title for ", r.URL.Path)
		c.lock.Lock()
		c.ids = append(c.ids, r.URL.Path)
		c.lock.Unlock()
	})

}
