package approvals

import (
	"context"
	"testing"
	"time"

	"github.com/keel-hq/keel/cache/memory"
	"github.com/keel-hq/keel/types"
)

func TestCreateApproval(t *testing.T) {
	mem := memory.NewMemoryCache()

	am := New(mem)

	err := am.Create(&types.Approval{
		Provider:       types.ProviderTypeKubernetes,
		Identifier:     "xxx/app-1",
		CurrentVersion: "1.2.3",
		NewVersion:     "1.2.5",
		Deadline:       time.Now().Add(5 * time.Minute),
	})

	if err != nil {
		t.Fatalf("failed to create approval: %s", err)
	}

	stored, err := am.Get("xxx/app-1")
	if err != nil {
		t.Fatalf("failed to get approval: %s", err)
	}

	if stored.CurrentVersion != "1.2.3" {
		t.Errorf("unexpected version: %s", stored.CurrentVersion)
	}
}

func TestDeleteApproval(t *testing.T) {
	mem := memory.NewMemoryCache()

	am := New(mem)

	err := am.Create(&types.Approval{
		Provider:       types.ProviderTypeKubernetes,
		Identifier:     "xxx/app-1",
		CurrentVersion: "1.2.3",
		NewVersion:     "1.2.5",
		Deadline:       time.Now().Add(5 * time.Minute),
	})

	if err != nil {
		t.Fatalf("failed to create approval: %s", err)
	}

	err = am.Delete("xxx/app-1")
	if err != nil {
		t.Errorf("failed to delete approval: %s", err)
	}

	_, err = am.Get("xxx/app-1")
	if err == nil {
		t.Errorf("expected to get an error when retrieving deleted approval")
	}

}

func TestUpdateApproval(t *testing.T) {
	mem := memory.NewMemoryCache()

	am := New(mem)

	err := am.Create(&types.Approval{
		Provider:       types.ProviderTypeKubernetes,
		Identifier:     "xxx/app-1",
		CurrentVersion: "1.2.3",
		NewVersion:     "1.2.5",
		VotesRequired:  1,
		VotesReceived:  0,
		Deadline:       time.Now().Add(5 * time.Minute),
		Event: &types.Event{
			Repository: types.Repository{
				Name: "very/repo",
				Tag:  "1.2.5",
			},
		},
	})

	if err != nil {
		t.Fatalf("failed to create approval: %s", err)
	}
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	ch, err := am.SubscribeApproved(ctx)
	if err != nil {
		t.Fatalf("failed to subscribe: %s", err)
	}

	err = am.Update(&types.Approval{
		Provider:       types.ProviderTypeKubernetes,
		Identifier:     "xxx/app-1",
		CurrentVersion: "1.2.3",
		NewVersion:     "1.2.5",
		VotesRequired:  1,
		VotesReceived:  1,
		Deadline:       time.Now().Add(5 * time.Minute),
		Event: &types.Event{
			Repository: types.Repository{
				Name: "very/repo",
				Tag:  "1.2.5",
			},
		},
	})

	approved := <-ch

	if approved.Event.Repository.Name != "very/repo" {
		t.Errorf("unexpected repo name in re-submitted event: %s", approved.Event.Repository.Name)
	}
	if approved.Event.Repository.Tag != "1.2.5" {
		t.Errorf("unexpected repo tag in re-submitted event: %s", approved.Event.Repository.Tag)
	}
}

func TestUpdateApprovalRejected(t *testing.T) {
	mem := memory.NewMemoryCache()

	am := New(mem)

	err := am.Create(&types.Approval{
		Provider:       types.ProviderTypeKubernetes,
		Identifier:     "xxx/app-1",
		CurrentVersion: "1.2.3",
		NewVersion:     "1.2.5",
		VotesRequired:  1,
		VotesReceived:  0,
		Deadline:       time.Now().Add(5 * time.Minute),
		Event: &types.Event{
			Repository: types.Repository{
				Name: "very/repo",
				Tag:  "1.2.5",
			},
		},
	})

	if err != nil {
		t.Fatalf("failed to create approval: %s", err)
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	ch, err := am.SubscribeApproved(ctx)
	if err != nil {
		t.Fatalf("failed to subscribe: %s", err)
	}

	// rejecting
	err = am.Update(&types.Approval{
		Provider:       types.ProviderTypeKubernetes,
		Identifier:     "xxx/app-1",
		CurrentVersion: "1.2.3",
		NewVersion:     "1.2.5",
		VotesRequired:  1,
		VotesReceived:  0,
		Rejected:       true,
		Deadline:       time.Now().Add(5 * time.Minute),
		Event: &types.Event{
			Repository: types.Repository{
				Name: "very/repo",
				Tag:  "1.2.5",
			},
		},
	})
	if err != nil {
		t.Fatalf("failed to update approval: %s", err)
	}

	// sending vote
	err = am.Update(&types.Approval{
		Provider:       types.ProviderTypeKubernetes,
		Identifier:     "xxx/app-1",
		CurrentVersion: "1.2.3",
		NewVersion:     "1.2.5",
		VotesRequired:  1,
		VotesReceived:  1,
		Rejected:       true,
		Deadline:       time.Now().Add(5 * time.Minute),
		Event: &types.Event{
			Repository: types.Repository{
				Name: "very/repo",
				Tag:  "1.2.5",
			},
		},
	})
	if err != nil {
		t.Fatalf("failed to update approval: %s", err)
	}

	select {
	case <-time.After(500 * time.Millisecond):
		// success
		return
	case approval := <-ch:
		t.Errorf("unexpected approval got: %s", approval.Identifier)
	}

}

func TestApprove(t *testing.T) {
	mem := memory.NewMemoryCache()

	am := New(mem)

	err := am.Create(&types.Approval{
		Provider:       types.ProviderTypeKubernetes,
		Identifier:     "xxx/app-1:1.2.5",
		CurrentVersion: "1.2.3",
		NewVersion:     "1.2.5",
		Deadline:       time.Now().Add(5 * time.Minute),
		VotesRequired:  2,
		VotesReceived:  0,
	})

	if err != nil {
		t.Fatalf("failed to create approval: %s", err)
	}

	am.Approve("xxx/app-1:1.2.5", "warda")

	stored, err := am.Get("xxx/app-1:1.2.5")
	if err != nil {
		t.Fatalf("failed to get approval: %s", err)
	}

	if stored.VotesReceived != 1 {
		t.Errorf("unexpected number of received votes: %d", stored.VotesReceived)
	}
}

func TestApproveTwiceSameVoter(t *testing.T) {
	mem := memory.NewMemoryCache()

	am := New(mem)

	err := am.Create(&types.Approval{
		Provider:       types.ProviderTypeKubernetes,
		Identifier:     "xxx/app-1:1.2.5",
		CurrentVersion: "1.2.3",
		NewVersion:     "1.2.5",
		Deadline:       time.Now().Add(5 * time.Minute),
		VotesRequired:  2,
		VotesReceived:  0,
	})

	if err != nil {
		t.Fatalf("failed to create approval: %s", err)
	}

	am.Approve("xxx/app-1:1.2.5", "warda")
	am.Approve("xxx/app-1:1.2.5", "warda")

	stored, err := am.Get("xxx/app-1:1.2.5")
	if err != nil {
		t.Fatalf("failed to get approval: %s", err)
	}

	// should still be the same
	if stored.VotesReceived != 1 {
		t.Errorf("unexpected number of received votes: %d", stored.VotesReceived)
	}
}

func TestApproveTwoVoters(t *testing.T) {
	mem := memory.NewMemoryCache()

	am := New(mem)

	err := am.Create(&types.Approval{
		Provider:       types.ProviderTypeKubernetes,
		Identifier:     "xxx/app-1:1.2.5",
		CurrentVersion: "1.2.3",
		NewVersion:     "1.2.5",
		Deadline:       time.Now().Add(5 * time.Minute),
		VotesRequired:  2,
		VotesReceived:  0,
	})

	if err != nil {
		t.Fatalf("failed to create approval: %s", err)
	}

	am.Approve("xxx/app-1:1.2.5", "w")
	am.Approve("xxx/app-1:1.2.5", "k")

	stored, err := am.Get("xxx/app-1:1.2.5")
	if err != nil {
		t.Fatalf("failed to get approval: %s", err)
	}

	// should still be the same
	if stored.VotesReceived != 2 {
		t.Errorf("unexpected number of received votes: %d", stored.VotesReceived)
	}
}

func TestReject(t *testing.T) {
	mem := memory.NewMemoryCache()

	am := New(mem)

	err := am.Create(&types.Approval{
		Provider:       types.ProviderTypeKubernetes,
		Identifier:     "xxx/app-1",
		CurrentVersion: "1.2.3",
		NewVersion:     "1.2.5",
		Deadline:       time.Now().Add(5 * time.Minute),
		VotesRequired:  2,
		VotesReceived:  0,
	})

	if err != nil {
		t.Fatalf("failed to create approval: %s", err)
	}

	am.Reject("xxx/app-1")

	stored, err := am.Get("xxx/app-1")
	if err != nil {
		t.Fatalf("failed to get approval: %s", err)
	}

	if !stored.Rejected {
		t.Errorf("unexpected approval to be rejected")
	}
}

func TestExpire(t *testing.T) {
	mem := memory.NewMemoryCache()

	am := New(mem)

	err := am.Create(&types.Approval{
		Provider:       types.ProviderTypeKubernetes,
		Identifier:     "xxx/app-1",
		CurrentVersion: "1.2.3",
		NewVersion:     "1.2.5",
		Deadline:       time.Now().Add(-5 * time.Minute),
		VotesRequired:  2,
		VotesReceived:  0,
	})

	if err != nil {
		t.Fatalf("failed to create approval: %s", err)
	}

	err = am.expireEntries()
	if err != nil {
		t.Errorf("got error while expiring entries: %s", err)
	}

	_, err = am.Get("xxx/app-1")
	if err == nil {
		t.Errorf("expected approval to be deleted but didn't get an error")
	}
}
