package container

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"

	"github.com/docker/docker/api/types"
	containertypes "github.com/docker/docker/api/types/container"
	"github.com/pborman/uuid"
	"github.com/stretchr/testify/assert"
)

var root string

func TestMain(m *testing.M) {
	var err error
	root, err = ioutil.TempDir("", "docker-container-test-")
	if err != nil {
		panic(err)
	}
	defer os.RemoveAll(root)

	os.Exit(m.Run())
}

func newContainer(t *testing.T) *Container {
	var (
		id    = uuid.New()
		cRoot = filepath.Join(root, id)
	)
	if err := os.MkdirAll(cRoot, 0755); err != nil {
		t.Fatal(err)
	}
	c := NewBaseContainer(id, cRoot)
	c.HostConfig = &containertypes.HostConfig{}
	return c
}

func TestViewSaveDelete(t *testing.T) {
	db, err := NewViewDB()
	if err != nil {
		t.Fatal(err)
	}
	c := newContainer(t)
	if err := c.CheckpointTo(db); err != nil {
		t.Fatal(err)
	}
	if err := db.Delete(c); err != nil {
		t.Fatal(err)
	}
}

func TestViewAll(t *testing.T) {
	var (
		db, _ = NewViewDB()
		one   = newContainer(t)
		two   = newContainer(t)
	)
	one.Pid = 10
	if err := one.CheckpointTo(db); err != nil {
		t.Fatal(err)
	}
	two.Pid = 20
	if err := two.CheckpointTo(db); err != nil {
		t.Fatal(err)
	}

	all, err := db.Snapshot().All()
	if err != nil {
		t.Fatal(err)
	}
	if l := len(all); l != 2 {
		t.Fatalf("expected 2 items, got %d", l)
	}
	byID := make(map[string]Snapshot)
	for i := range all {
		byID[all[i].ID] = all[i]
	}
	if s, ok := byID[one.ID]; !ok || s.Pid != 10 {
		t.Fatalf("expected something different with for id=%s: %v", one.ID, s)
	}
	if s, ok := byID[two.ID]; !ok || s.Pid != 20 {
		t.Fatalf("expected something different with for id=%s: %v", two.ID, s)
	}
}

func TestViewGet(t *testing.T) {
	var (
		db, _ = NewViewDB()
		one   = newContainer(t)
	)
	one.ImageID = "some-image-123"
	if err := one.CheckpointTo(db); err != nil {
		t.Fatal(err)
	}
	s, err := db.Snapshot().Get(one.ID)
	if err != nil {
		t.Fatal(err)
	}
	if s == nil || s.ImageID != "some-image-123" {
		t.Fatalf("expected ImageID=some-image-123. Got: %v", s)
	}
}

func TestNames(t *testing.T) {
	db, err := NewViewDB()
	if err != nil {
		t.Fatal(err)
	}
	assert.NoError(t, db.ReserveName("name1", "containerid1"))
	assert.NoError(t, db.ReserveName("name1", "containerid1")) // idempotent
	assert.NoError(t, db.ReserveName("name2", "containerid2"))
	assert.EqualError(t, db.ReserveName("name2", "containerid3"), ErrNameReserved.Error())

	// Releasing a name allows the name to point to something else later.
	assert.NoError(t, db.ReleaseName("name2"))
	assert.NoError(t, db.ReserveName("name2", "containerid3"))

	view := db.Snapshot()

	id, err := view.GetID("name1")
	assert.NoError(t, err)
	assert.Equal(t, "containerid1", id)

	id, err = view.GetID("name2")
	assert.NoError(t, err)
	assert.Equal(t, "containerid3", id)

	_, err = view.GetID("notreserved")
	assert.EqualError(t, err, ErrNameNotReserved.Error())

	// Releasing and re-reserving a name doesn't affect the snapshot.
	assert.NoError(t, db.ReleaseName("name2"))
	assert.NoError(t, db.ReserveName("name2", "containerid4"))

	id, err = view.GetID("name1")
	assert.NoError(t, err)
	assert.Equal(t, "containerid1", id)

	id, err = view.GetID("name2")
	assert.NoError(t, err)
	assert.Equal(t, "containerid3", id)

	// GetAllNames
	assert.Equal(t, map[string][]string{"containerid1": {"name1"}, "containerid3": {"name2"}}, view.GetAllNames())

	assert.NoError(t, db.ReserveName("name3", "containerid1"))
	assert.NoError(t, db.ReserveName("name4", "containerid1"))

	view = db.Snapshot()
	assert.Equal(t, map[string][]string{"containerid1": {"name1", "name3", "name4"}, "containerid4": {"name2"}}, view.GetAllNames())

	// Release containerid1's names with Delete even though no container exists
	assert.NoError(t, db.Delete(&Container{ID: "containerid1"}))

	// Reusing one of those names should work
	assert.NoError(t, db.ReserveName("name1", "containerid4"))
	view = db.Snapshot()
	assert.Equal(t, map[string][]string{"containerid4": {"name1", "name2"}}, view.GetAllNames())
}

// Test case for GitHub issue 35920
func TestViewWithHealthCheck(t *testing.T) {
	var (
		db, _ = NewViewDB()
		one   = newContainer(t)
	)
	one.Health = &Health{
		Health: types.Health{
			Status: "starting",
		},
	}
	if err := one.CheckpointTo(db); err != nil {
		t.Fatal(err)
	}
	s, err := db.Snapshot().Get(one.ID)
	if err != nil {
		t.Fatal(err)
	}
	if s == nil || s.Health != "starting" {
		t.Fatalf("expected Health=starting. Got: %+v", s)
	}
}