package sync_test

import (
	"fmt"
	"log"
	"testing"
	"time"

	"github.com/remogatto/prettytest"
	karmen_client "gogs.carducci-dante.gov.it/karmen/client"
	"gogs.carducci-dante.gov.it/karmen/core/config"
	"gogs.carducci-dante.gov.it/karmen/core/cron/sync"
	"gogs.carducci-dante.gov.it/karmen/core/orm"
	karmen_ldap "gogs.carducci-dante.gov.it/karmen/ldap"
)

type testSuite struct {
	prettytest.Suite
}

var (
	ldap   *karmen_ldap.Client
	karmen *karmen_client.Client
	conf   *config.ConfigT
)

func TestRunner(t *testing.T) {
	prettytest.Run(
		t,
		new(testSuite),
	)
}

func (t *testSuite) BeforeAll() {
	var err error

	time.Sleep(time.Second * 10) // Wait for the server to go up...

	conf = new(config.ConfigT)

	err = config.ReadFile("config.yaml", conf)
	if err != nil {
		panic(err)
	}

	ldap, err = karmen_ldap.NewClient("localhost:389", conf)
	if err != nil {
		panic(err)
	}

	karmen, err = karmen_client.Dial(conf.Url, conf.Admin.Username, conf.Admin.Password)
	if err != nil {
		log.Println(err)
	}

}

func (t *testSuite) AfterAll() {
	ldap.Close()
}

func (t *testSuite) TestSyncUsers() {
	syncJob := sync.NewSyncJob(conf)

	teachers, err := karmen.GetTeachers()
	t.Nil(err)

	entries, err := ldap.Users(conf.Sync.TeachersSearchBase)
	t.Nil(err)

	users := make([]orm.User, 0)
	for _, teacher := range teachers {
		users = append(users, teacher)
	}

	result, err := syncJob.SyncUsers(ldap, karmen, entries, users)
	t.Nil(err)

	if !t.Failed() {
		t.Equal(len(result.Added), 11)
		t.Equal(len(result.Updated), 0)
		t.Equal(len(result.Removed), 1)

		t.Equal(result.Removed[0], "cn=John DOE,ou=Teachers,ou=People,dc=foo,dc=org")
	}

	// Run sync two times should result in no updating

	teachers, err = karmen.GetTeachers()
	t.Nil(err)

	entries, err = ldap.Users(conf.Sync.TeachersSearchBase)
	t.Nil(err)

	users = make([]orm.User, 0)
	for _, teacher := range teachers {
		users = append(users, teacher)
	}

	result, err = syncJob.SyncUsers(ldap, karmen, entries, users)
	t.Nil(err)

	if !t.Failed() {
		t.Equal(len(result.Added), 0)
		t.Equal(len(result.Updated), 0)
		t.Equal(len(result.Removed), 0)
	}

	// Update user and test LDAP sync

	user := teachers[0]

	user.SetRegenerate(true)

	err = karmen.UpdateUser(user)
	t.Nil(err)

	teachers, err = karmen.GetTeachers()
	t.Nil(err)

	entries, err = ldap.Users(conf.Sync.TeachersSearchBase)
	t.Nil(err)

	users = make([]orm.User, 0)
	for _, teacher := range teachers {
		users = append(users, teacher)
	}
	result, err = syncJob.SyncUsers(ldap, karmen, entries, users)
	t.Nil(err)

	if !t.Failed() {
		t.Equal(len(result.Added), 0)
		t.Equal(len(result.Updated), 1)
		t.Equal(len(result.Removed), 0)
	}

	// Sync administratives

	administratives, err := karmen.GetAdministratives()
	t.Nil(err)

	entries, err = ldap.Users(conf.Sync.AdministrativesSearchBase)
	t.Nil(err)

	users = make([]orm.User, 0)
	for _, administrative := range administratives {
		users = append(users, administrative)
	}

	result, err = syncJob.SyncUsers(ldap, karmen, entries, users)
	t.Nil(err)

	if !t.Failed() {
		t.Equal(2, len(result.Added))
		t.Equal(0, len(result.Updated))
		t.Equal(0, len(result.Removed))
	}

}

func (t *testSuite) TestSyncGroup() {
	syncJob := sync.NewSyncJob(conf)

	teachers, err := karmen.GetTeachers()
	t.Nil(err)

	users := make([]orm.User, 0)
	for _, teacher := range teachers {
		users = append(users, teacher)
	}

	result, err := syncJob.SyncGroup(ldap, users, conf.Sync.TeachersGroup)
	t.Nil(err)

	if !t.Failed() {
		t.Equal(11, len(result.Added))
		t.Equal(0, len(result.Updated))
		t.Equal(0, len(result.Removed))
	}

	// Running twice should result in no update

	result, err = syncJob.SyncGroup(ldap, users, conf.Sync.TeachersGroup)
	t.Nil(err)

	if !t.Failed() {
		t.Equal(0, len(result.Added))
		t.Equal(0, len(result.Updated))
		t.Equal(0, len(result.Removed))
	}

	// Test ML sync

	result, err = syncJob.SyncGroup(ldap, users, conf.Sync.TeachersML)
	t.Nil(err)

	if !t.Failed() {
		t.Equal(11, len(result.Added))
		t.Equal(0, len(result.Updated))
		t.Equal(1, len(result.Removed))
	}

	// Running twice should result in no update

	result, err = syncJob.SyncGroup(ldap, users, conf.Sync.TeachersML)
	t.Nil(err)

	if !t.Failed() {
		t.Equal(0, len(result.Added))
		t.Equal(0, len(result.Updated))
		t.Equal(0, len(result.Removed))
	}

	administratives, err := karmen.GetAdministratives()
	t.Nil(err)

	users = make([]orm.User, 0)
	for _, administrative := range administratives {
		users = append(users, administrative)
	}

	result, err = syncJob.SyncGroup(ldap, users, conf.Sync.AdministrativesGroup)
	t.Nil(err)

	if !t.Failed() {
		t.Equal(2, len(result.Added))
		t.Equal(0, len(result.Updated))
		t.Equal(0, len(result.Removed))
	}

	departments, err := karmen.GetDepartments()
	t.Nil(err)

	if !t.Failed() {

		results := make(map[string][]int)

		results["cn=SOSTEGNO,ou=Mailing Lists"] = []int{2, 0, 1}
		results["cn=SOSTEGNO,ou=Dipartimenti"] = []int{2, 0, 0}

		results["cn=LINGUE STRANIERE,ou=Mailing Lists"] = []int{1, 0, 1}
		results["cn=LINGUE STRANIERE,ou=Dipartimenti"] = []int{1, 0, 0}

		for _, department := range departments {
			users := make([]orm.User, 0)
			for _, teacher := range department.Teachers {
				users = append(users, teacher)
			}
			group := fmt.Sprintf("cn=%s,ou=Mailing Lists", department.Name)
			result, err := syncJob.SyncGroup(ldap, users, group)
			t.Nil(err)

			if !t.Failed() {
				t.Equal(results[group][0], len(result.Added))
				t.Equal(results[group][1], len(result.Updated))
				t.Equal(results[group][2], len(result.Removed))
			}

			group = fmt.Sprintf("cn=%s,ou=Dipartimenti", department.Name)
			result, err = syncJob.SyncGroup(ldap, users, group)
			t.Nil(err)

			if !t.Failed() {
				t.Equal(results[group][0], len(result.Added))
				t.Equal(results[group][1], len(result.Updated))
				t.Equal(results[group][2], len(result.Removed))
			}

		}
	}

}
