package container

import (
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
)

func writeTempFile(t *testing.T, input string) string {
	tempFile, createTempError := os.CreateTemp("", "syncuids-*")
	assert.NoError(t, createTempError)
	_, writeError := tempFile.Write([]byte(input))
	assert.NoError(t, writeError)
	assert.NoError(t, tempFile.Sync())
	return tempFile.Name()
}

func makeSyncDB(t *testing.T, hostInput string, containerInput string) syncDB {
	hostFileName := writeTempFile(t, hostInput)
	defer os.Remove(hostFileName)
	containerFileName := writeTempFile(t, containerInput)
	defer os.Remove(containerFileName)
	db := make(syncDB)
	var err error
	err = db.readFromHost(hostFileName)
	assert.NoError(t, err)
	err = db.readFromContainer(containerFileName)
	assert.NoError(t, err)
	return db
}

func Test_readFromHost_single(t *testing.T) {
	hostFileName := writeTempFile(t, `testuser:x:1001:1001::/home/testuser:/bin/bash`)
	defer os.Remove(hostFileName)

	db := make(syncDB)
	err := db.readFromHost(hostFileName)
	assert.NoError(t, err)

	assert.Len(t, db, 1)
	assert.Equal(t, 1001, db["testuser"].HostID)
	assert.Equal(t, -1, db["testuser"].ContainerID)
}

func Test_readFromHost_multiple(t *testing.T) {
	hostFileName := writeTempFile(t, `
testuser1:x:1001:1001::/home/testuser:/bin/bash
testuser2:x:1002:1002::/home/testuser:/bin/bash
`)
	defer os.Remove(hostFileName)

	db := make(syncDB)
	err := db.readFromHost(hostFileName)
	assert.NoError(t, err)

	assert.Len(t, db, 2)
	assert.Equal(t, 1001, db["testuser1"].HostID)
	assert.Equal(t, -1, db["testuser1"].ContainerID)
	assert.Equal(t, 1002, db["testuser2"].HostID)
	assert.Equal(t, -1, db["testuser2"].ContainerID)
}

func Test_readFromContainer_single(t *testing.T) {
	containerFileName := writeTempFile(t, `testuser:x:1001:1001::/home/testuser:/bin/bash`)
	defer os.Remove(containerFileName)

	db := make(syncDB)
	err := db.readFromContainer(containerFileName)
	assert.NoError(t, err)

	assert.Len(t, db, 1)
	assert.Equal(t, 1001, db["testuser"].ContainerID)
	assert.Equal(t, -1, db["testuser"].HostID)
}

func Test_readFromContainer_multiple(t *testing.T) {
	containerFileName := writeTempFile(t, `
testuser1:x:1001:1001::/home/testuser:/bin/bash
testuser2:x:1002:1002::/home/testuser:/bin/bash
`)
	defer os.Remove(containerFileName)

	db := make(syncDB)
	err := db.readFromContainer(containerFileName)
	assert.NoError(t, err)
	assert.Len(t, db, 2)
	assert.Equal(t, 1001, db["testuser1"].ContainerID)
	assert.Equal(t, -1, db["testuser1"].HostID)
	assert.Equal(t, 1002, db["testuser2"].ContainerID)
	assert.Equal(t, -1, db["testuser2"].HostID)
}

func Test_readFromBoth_multiple(t *testing.T) {
	containerFileName := writeTempFile(t, `
testuser1:x:1001:1001::/home/testuser:/bin/bash
testuser2:x:1002:1002::/home/testuser:/bin/bash
`)
	defer os.Remove(containerFileName)

	hostFileName := writeTempFile(t, `
testuser1:x:2001:2001::/home/testuser:/bin/bash
testuser3:x:2003:2003::/home/testuser:/bin/bash
`)
	defer os.Remove(hostFileName)

	db := make(syncDB)
	var err error
	err = db.readFromContainer(containerFileName)
	assert.NoError(t, err)
	err = db.readFromHost(hostFileName)
	assert.NoError(t, err)
	assert.Len(t, db, 3)
	assert.Equal(t, 1001, db["testuser1"].ContainerID)
	assert.Equal(t, 2001, db["testuser1"].HostID)
	assert.Equal(t, 1002, db["testuser2"].ContainerID)
	assert.Equal(t, -1, db["testuser2"].HostID)
	assert.Equal(t, -1, db["testuser3"].ContainerID)
	assert.Equal(t, 2003, db["testuser3"].HostID)
}

func Test_checkConflicts_empty(t *testing.T) {
	db := makeSyncDB(t, "", "")
	assert.NoError(t, db.checkConflicts())
}

func Test_checkConflicts_single(t *testing.T) {
	db := makeSyncDB(t, "", "testuser:x:1001:1001::/home/testuser:/bin/bash")
	assert.NoError(t, db.checkConflicts())
}

func Test_checkConflicts_match(t *testing.T) {
	db := makeSyncDB(t,
		"testuser:x:1001:1001::/home/testuser:/bin/bash",
		"testuser:x:1001:1001::/home/testuser:/bin/bash")
	assert.NoError(t, db.checkConflicts())
}

func Test_checkConflicts_conflict(t *testing.T) {
	db := makeSyncDB(t,
		"testuser2:x:1001:1001::/home/testuser:/bin/bash",
		"testuser1:x:1001:1001::/home/testuser:/bin/bash")
	assert.Error(t, db.checkConflicts())
}

func Test_getOnlyContainerLines(t *testing.T) {
	containerFileName := writeTempFile(t, `
testuser1:x:1001:1001::/home/testuser:/bin/bash
testuser2:x:1002:1002::/home/testuser:/bin/bash
`)
	defer os.Remove(containerFileName)

	hostFileName := writeTempFile(t, `
testuser1:x:2001:2001::/home/testuser:/bin/bash
testuser3:x:2003:2003::/home/testuser:/bin/bash
`)
	defer os.Remove(hostFileName)

	db := make(syncDB)
	var err error
	err = db.readFromContainer(containerFileName)
	assert.NoError(t, err)
	err = db.readFromHost(hostFileName)
	assert.NoError(t, err)

	lines, err := db.getOnlyContainerLines(containerFileName)
	assert.NoError(t, err)

	assert.Len(t, lines, 1)
	assert.Equal(t, lines[0], "testuser2:x:1002:1002::/home/testuser:/bin/bash")
}

func Test_needsSync_empty(t *testing.T) {
	db := makeSyncDB(t, "", "")
	assert.False(t, db.needsSync())
}

func Test_needsSync_containerOnly(t *testing.T) {
	db := makeSyncDB(t, "", `
testuser1:x:1001:1001::/home/testuser:/bin/bash
testuser2:x:1002:1002::/home/testuser:/bin/bash`)

	assert.False(t, db.needsSync())
}

func Test_needsSync_hostOnly(t *testing.T) {
	db := makeSyncDB(t, `
testuser1:x:1001:1001::/home/testuser:/bin/bash
testuser2:x:1002:1002::/home/testuser:/bin/bash`, "")

	assert.True(t, db.needsSync())
}

func Test_needsSync_match(t *testing.T) {
	db := makeSyncDB(t,
		"testuser:x:1001:1001::/home/testuser:/bin/bash",
		"testuser:x:1001:1001::/home/testuser:/bin/bash")

	assert.False(t, db.needsSync())
}

func Test_needsSync_differ(t *testing.T) {
	db := makeSyncDB(t,
		`
testuser1:x:2001:2001::/home/testuser:/bin/bash
testuser3:x:2003:2003::/home/testuser:/bin/bash`,
		`
testuser1:x:1001:1001::/home/testuser:/bin/bash
testuser2:x:1002:1002::/home/testuser:/bin/bash`)

	assert.True(t, db.needsSync())
}

func Test_onlyHost(t *testing.T) {
	db := makeSyncDB(t, "testuser1:x:2001:2001::/home/testuser:/bin/bash", "")
	entry := db["testuser1"]
	assert.True(t, entry.inHost())
	assert.False(t, entry.inContainer())
	assert.True(t, entry.onlyHost())
	assert.False(t, entry.onlyContainer())
	assert.False(t, entry.match())
	assert.False(t, entry.differ())
}

func Test_onlyContainer(t *testing.T) {
	db := makeSyncDB(t, "", "testuser1:x:2001:2001::/home/testuser:/bin/bash")
	entry := db["testuser1"]
	assert.False(t, entry.inHost())
	assert.True(t, entry.inContainer())
	assert.False(t, entry.onlyHost())
	assert.True(t, entry.onlyContainer())
	assert.False(t, entry.match())
	assert.False(t, entry.differ())
}

func Test_match(t *testing.T) {
	db := makeSyncDB(t,
		"testuser1:x:2001:2001::/home/testuser:/bin/bash",
		"testuser1:x:2001:2001::/home/testuser:/bin/bash")
	entry := db["testuser1"]
	assert.True(t, entry.inHost())
	assert.True(t, entry.inContainer())
	assert.False(t, entry.onlyHost())
	assert.False(t, entry.onlyContainer())
	assert.True(t, entry.match())
	assert.False(t, entry.differ())
}

func Test_differ(t *testing.T) {
	db := makeSyncDB(t,
		"testuser1:x:1001:1001::/home/testuser:/bin/bash",
		"testuser1:x:2001:2001::/home/testuser:/bin/bash")
	entry := db["testuser1"]
	assert.True(t, entry.inHost())
	assert.True(t, entry.inContainer())
	assert.False(t, entry.onlyHost())
	assert.False(t, entry.onlyContainer())
	assert.False(t, entry.match())
	assert.True(t, entry.differ())
}

func Test_malformed_passwd(t *testing.T) {
	hostInput := `"testuser1:x:1001:1001::/home/testuser:/bin/bash"
	asdf`
	hostFileName := writeTempFile(t, hostInput)
	defer os.Remove(hostFileName)
	db := make(syncDB)
	err := db.readFromHost(hostFileName)
	assert.NoError(t, err)
}
