package hipchat

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"testing"
	"time"

	h "github.com/daneharrigan/hipchat"

	"github.com/keel-hq/keel/approvals"
	b "github.com/keel-hq/keel/bot"
	"github.com/keel-hq/keel/pkg/store/sql"

	"github.com/keel-hq/keel/provider/kubernetes"
	"github.com/keel-hq/keel/types"

	testutil "github.com/keel-hq/keel/util/testing"

	log "github.com/sirupsen/logrus"
)

type fakeProvider struct {
	submitted []types.Event
	images    []*types.TrackedImage
}

func (p *fakeProvider) Submit(event types.Event) error {
	p.submitted = append(p.submitted, event)
	return nil
}

func (p *fakeProvider) TrackedImages() ([]*types.TrackedImage, error) {
	return p.images, nil
}

func (p *fakeProvider) List() []string {
	return []string{"fakeprovider"}
}
func (p *fakeProvider) Stop() {
	return
}
func (p *fakeProvider) GetName() string {
	return "fp"
}

var botMessagesChannel chan *b.BotMessage
var approvalsRespCh chan *b.ApprovalResponse

type postedMessage struct {
	channel string
	text    string
}

type fakeXmppImplementer struct {
	postedMessages []postedMessage
	messages       chan *h.Message
}

func (i *fakeXmppImplementer) messageFromChat(message string) {
	i.messages <- &h.Message{
		Body: "@keel " + message,
		From: "111111_approvals@conf.hipchat.com/test",
		To:   "222222_333333@chat.hipchat.com/bot",
	}
}

func (i *fakeXmppImplementer) Say(roomID, name, body string) {
	i.postedMessages = append(i.postedMessages, postedMessage{
		text:    body,
		channel: roomID,
	})
}
func (i *fakeXmppImplementer) Status(s string) {
}
func (i *fakeXmppImplementer) Join(roomID, resource string) {
}
func (i *fakeXmppImplementer) KeepAlive() {
}
func (i *fakeXmppImplementer) Messages() <-chan *h.Message {
	return i.messages
}

func NewBot(k8sImplementer kubernetes.Implementer,
	approvalsManager approvals.Manager, fi XmppImplementer) *Bot {

	approvalsRespCh = make(chan *b.ApprovalResponse)
	botMessagesChannel = make(chan *b.BotMessage)
	fakeBot := &Bot{}
	fakeBot.hipchatClient = fi

	os.Setenv("HIPCHAT_APPROVALS_CHANNEL", "111111_approvals@conf.hipchat.com")
	os.Setenv("HIPCHAT_APPROVALS_BOT_NAME", "keel")
	os.Setenv("HIPCHAT_APPROVALS_USER_NAME", "111111_222222")
	os.Setenv("HIPCHAT_APPROVALS_PASSWORT", "pass")
	os.Setenv("HIPCHAT_CONNECTION_ATTEMPTS", "0")

	b.RegisterBot("fakechat", fakeBot)
	b.Run(k8sImplementer, approvalsManager)
	return fakeBot
}

func init() {
	log.SetLevel(log.DebugLevel)
}

func newTestingUtils() (*sql.SQLStore, func()) {
	dir, err := ioutil.TempDir("", "whstoretest")
	if err != nil {
		log.Fatal(err)
	}
	tmpfn := filepath.Join(dir, "gorm.db")
	// defer
	store, err := sql.New(sql.Opts{DatabaseType: "sqlite3", URI: tmpfn})
	if err != nil {
		log.Fatal(err)
	}

	teardown := func() {
		os.RemoveAll(dir) // clean up
	}

	return store, teardown
}

func TestHelpCommand(t *testing.T) {
	f8s := &testutil.FakeK8sImplementer{}
	fi := &fakeXmppImplementer{}
	fi.messages = make(chan *h.Message)

	store, teardown := newTestingUtils()
	defer teardown()
	am := approvals.New(&approvals.Opts{
		Store: store,
	})

	NewBot(f8s, am, fi)
	defer b.Stop()

	time.Sleep(1 * time.Second)

	if len(fi.postedMessages) != 1 {
		t.Errorf("expected to find 1 message, but got: %d", len(fi.postedMessages))
	}
	if !strings.HasPrefix(fi.postedMessages[0].text, "Keel bot was started") {
		t.Errorf("expected to find greeting message, but got: %s", fi.postedMessages[0].text)
	}

	fi.messageFromChat("help")
	time.Sleep(1 * time.Second)

	if len(fi.postedMessages) != 2 {
		t.Errorf("expected to find 2 messages, but got: %d", len(fi.postedMessages))
	}
	if !strings.HasPrefix(fi.postedMessages[1].text, "/code Here's a list of supported commands") {
		t.Errorf("expected to find help message, but got: %s", fi.postedMessages[1].text)
	}
}

func TestBotAproval(t *testing.T) {
	f8s := &testutil.FakeK8sImplementer{}
	fi := &fakeXmppImplementer{}
	fi.messages = make(chan *h.Message)
	store, teardown := newTestingUtils()
	defer teardown()
	am := approvals.New(&approvals.Opts{
		Store: store,
	})

	NewBot(f8s, am, fi)
	defer b.Stop()

	time.Sleep(1 * time.Second)

	err := am.Create(&types.Approval{
		Identifier:     "k8s/project/repo:1.2.3",
		VotesRequired:  1,
		CurrentVersion: "2.3.4",
		NewVersion:     "3.4.5",
		Event: &types.Event{
			Repository: types.Repository{
				Name: "project/repo",
				Tag:  "2.3.4",
			},
		},
	})

	if err != nil {
		t.Fatalf("unexpected error while creating : %s", err)
	}

	time.Sleep(1 * time.Second)

	if len(fi.postedMessages) != 2 {
		t.Errorf("expected to find 2 message, but got: %d", len(fi.postedMessages))
	}
	if !strings.HasPrefix(fi.postedMessages[1].text, "/code Approval required!") {
		t.Errorf("expected to find help message, but got: %s", fi.postedMessages[1].text)
	}

	// approve
	fi.messageFromChat("approve k8s/project/repo:1.2.3")
	time.Sleep(1 * time.Second)

	if len(fi.postedMessages) != 3 {
		t.Errorf("expected to find 3 message, but got: %d", len(fi.postedMessages))
	}
	if !strings.HasPrefix(fi.postedMessages[2].text, "/code Update approved!") {
		t.Errorf("expected to find message, but got: %s", fi.postedMessages[2].text)
	}

	// get approvals
	fi.messageFromChat("get approvals")
	time.Sleep(1 * time.Second)
	if len(fi.postedMessages) != 4 {
		t.Errorf("expected to find 4 message, but got: %d", len(fi.postedMessages))
	}
	resp := trimSpaces(fi.postedMessages[3].text)

	if !strings.Contains(resp, "k8s/project/repo:1.2.3 2.3.4 -> 3.4.5 1/1 false") {
		t.Errorf("expected to find message, but got: %s", resp)
	}
}

func TestBotReject(t *testing.T) {
	f8s := &testutil.FakeK8sImplementer{}
	fi := &fakeXmppImplementer{}
	fi.messages = make(chan *h.Message)
	store, teardown := newTestingUtils()
	defer teardown()
	am := approvals.New(&approvals.Opts{
		Store: store,
	})

	NewBot(f8s, am, fi)
	defer b.Stop()

	time.Sleep(1 * time.Second)

	err := am.Create(&types.Approval{
		Identifier:     "k8s/project/repo:1.2.3",
		VotesRequired:  1,
		CurrentVersion: "2.3.4",
		NewVersion:     "3.4.5",
		Event: &types.Event{
			Repository: types.Repository{
				Name: "project/repo",
				Tag:  "2.3.4",
			},
		},
	})

	if err != nil {
		t.Fatalf("unexpected error while creating : %s", err)
	}

	time.Sleep(1 * time.Second)

	if len(fi.postedMessages) != 2 {
		t.Errorf("expected to find 2 message, but got: %d", len(fi.postedMessages))
	}
	if !strings.HasPrefix(fi.postedMessages[1].text, "/code Approval required!") {
		t.Errorf("expected to find help message, but got: %s", fi.postedMessages[1].text)
	}

	// reject
	fi.messageFromChat("reject k8s/project/repo:1.2.3")
	time.Sleep(1 * time.Second)

	if len(fi.postedMessages) != 3 {
		t.Errorf("expected to find 3 message, but got: %d", len(fi.postedMessages))
	}
	if !strings.HasPrefix(fi.postedMessages[2].text, "/code Change rejected") {
		t.Errorf("expected to find message, but got: %s", fi.postedMessages[2].text)
	}

	// get approvals
	fi.messageFromChat("get approvals")
	time.Sleep(1 * time.Second)
	if len(fi.postedMessages) != 4 {
		t.Errorf("expected to find 4 message, but got: %d", len(fi.postedMessages))
	}
	resp := trimSpaces(fi.postedMessages[3].text)

	if !strings.Contains(resp, "k8s/project/repo:1.2.3 2.3.4 -> 3.4.5 0/1 true") {
		t.Errorf("expected to find message, but got: %s", resp)
	}
}

func trimSpaces(input string) string {
	reLeadcloseWhtsp := regexp.MustCompile(`^[\s\p{Zs}]+|[\s\p{Zs}]+$`)
	reInsideWhtsp := regexp.MustCompile(`[\s\p{Zs}]{2,}`)
	final := reLeadcloseWhtsp.ReplaceAllString(input, "")
	final = reInsideWhtsp.ReplaceAllString(final, " ")
	return final
}
