package main

import (
	"fmt"
	"strconv"
	"strings"
	"unicode"

	"devel.mephi.ru/dyokunev/go-sdapi/sdApi1"
	"devel.mephi.ru/dyokunev/go-sdapi/sdApi11"
	"devel.mephi.ru/dyokunev/go-sdapi/sdApi12"
	"devel.mephi.ru/iacherepanov/openstack-gophercloud"
	"devel.mephi.ru/iacherepanov/openstack-gophercloud/openstack"
	"devel.mephi.ru/iacherepanov/openstack-gophercloud/openstack/identity/v3/projects"
	"devel.mephi.ru/iacherepanov/openstack-gophercloud/openstack/identity/v3/users"
	"github.com/alyu/configparser"

	asuModels "devel.mephi.ru/dyokunev/go-asu-models"
	api11Models "devel.mephi.ru/dyokunev/go-sd-models/api11"
	api12Models "devel.mephi.ru/dyokunev/go-sd-models/api12"
)

// H E L P E R S

func checkErr(err error) {
	if err != nil {
		panic(err)
	}
}

func pointerFromBool(b bool) *bool {
	return &b
}

func capitalize(str string) string {
	runes := []rune(str)
	runes[0] = unicode.ToUpper(runes[0])
	return string(runes)
}

// O P E N S T A C K   C L I E N T

type OpenstackIdentityClient struct {
	client       *gophercloud.ServiceClient
	config       *configparser.Configuration
	domainID     string
	userRoleID   string
	adminRoleID  string
	cloudAdminID string
}

func initIdentityClient() OpenstackIdentityClient {
	config, err := configparser.Read("./project_and_roles.conf")
	checkErr(err)
	section, err := config.Section("openstack")
	checkErr(err)

	domainID := section.ValueOf("projects_domain_id")
	userRoleID := section.ValueOf("user_role_id")
	adminRoleID := section.ValueOf("admin_role_id")
	cloudAdminID := section.ValueOf("cloud_admin_id")

	opts := gophercloud.AuthOptions{
		IdentityEndpoint: section.ValueOf("identity_endpoint"),
		DomainName:       section.ValueOf("domain_name"),
		TenantName:       section.ValueOf("project_name"),
		Username:         section.ValueOf("username"),
		Password:         section.ValueOf("password"),
	}

	provider, err := openstack.AuthenticatedClient(opts)
	checkErr(err)

	client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{
		Region: section.ValueOf("region"),
	})
	checkErr(err)

	osclient := OpenstackIdentityClient{
		client:       client,
		config:       config,
		domainID:     domainID,
		userRoleID:   userRoleID,
		adminRoleID:  adminRoleID,
		cloudAdminID: cloudAdminID,
	}

	return osclient
}

func (osclient OpenstackIdentityClient) getProjects() []projects.Project {
	listOpts := projects.ListOpts{
		DomainID: osclient.domainID,
		Enabled:  pointerFromBool(true),
		IsDomain: pointerFromBool(false),
	}

	allPages, err := projects.List(osclient.client, listOpts).AllPages()
	checkErr(err)

	allProjects, err := projects.ExtractProjects(allPages)
	checkErr(err)

	return allProjects
}

func (osclient OpenstackIdentityClient) getProjectsMap() map[int]projects.Project {
	allProjects := osclient.getProjects()
	return projectsToMap(allProjects)
}

func projectsToMap(allProjects []projects.Project) map[int]projects.Project {
	projectsMap := map[int]projects.Project{}

	for _, project := range allProjects {
		if project.Extra["PersNumber"] == nil {
			panic(fmt.Sprintf("Project %s has no persNumber", project.Name))
		}
		persNumber := int(project.Extra["PersNumber"].(float64))
		projectsMap[persNumber] = project
	}

	return projectsMap
}

func (osclient OpenstackIdentityClient) getOpenstackUsers() []users.User {
	listOpts := users.ListOpts{
		DomainID: osclient.domainID,
		Enabled:  pointerFromBool(true),
	}

	allPages, err := users.List(osclient.client, listOpts).AllPages()
	checkErr(err)

	allUsers, err := users.ExtractUsers(allPages)
	checkErr(err)

	return allUsers
}

func (osclient OpenstackIdentityClient) getOpenstackUsersMap() map[string]users.User {
	allUsers := osclient.getOpenstackUsers()
	openstackUsersMap := map[string]users.User{}

	for _, user := range allUsers {
		openstackUsersMap[user.Name] = user
	}

	return openstackUsersMap
}

func (osclient OpenstackIdentityClient) getOpenstackUsersMapByID() map[string]users.User {
	allUsers := osclient.getOpenstackUsers()
	openstackUsersMapByID := map[string]users.User{}

	for _, user := range allUsers {
		openstackUsersMapByID[user.ID] = user
	}

	return openstackUsersMapByID
}

func makeUpdateOpts(unit asuModels.Unit, domainID string) projects.UpdateOpts {
	updateOpts := projects.UpdateOpts{
		DomainID:    domainID,
		Enabled:     pointerFromBool(true),
		IsDomain:    pointerFromBool(false),
		Name:        fmt.Sprintf("%s [%v]", *unit.BriefName, *unit.PersNumber),
		Description: *unit.Name,
		Extra: map[string]interface{}{
			"ParentPersNumber": unit.ParentId},
	}

	return updateOpts
}

func makeCreateOpts(unit asuModels.Unit, domainID string) projects.CreateOpts {
	createOpts := projects.CreateOpts{
		DomainID:    domainID,
		Enabled:     pointerFromBool(true),
		IsDomain:    pointerFromBool(false),
		Name:        fmt.Sprintf("%s [%v]", *unit.BriefName, *unit.PersNumber),
		Description: *unit.Name,
		Extra: map[string]interface{}{
			"PersNumber":       unit.PersNumber,
			"ParentPersNumber": unit.ParentId},
	}

	return createOpts
}

// U N I T S

func getAllUnits(config *configparser.Configuration) asuModels.Units {
	section, err := config.Section("sd.mephi.ru")
	checkErr(err)

	sdApi1.SetApiKey(section.ValueOf("api1_key"))

	allUnits, err := sdApi1.GetUnits()
	checkErr(err)

	return allUnits
}

func getCleanUnitsPersNumberMap(config *configparser.Configuration) map[int]asuModels.Unit {
	allUnits := getAllUnits(config)
	idToPersNum := map[int]int{}
	cleanUnits := map[int]asuModels.Unit{}

	// Get all not nil units without duplicates
	for _, unit := range allUnits {
		if unit.PersNumber == nil || unit.IsHidden == 1 || unit.Name == nil || unit.BriefName == nil {
			continue
		}

		idToPersNum[unit.Id] = *unit.PersNumber
		existingUnit, ok := cleanUnits[*unit.PersNumber]
		if !ok {
			cleanUnits[*unit.PersNumber] = unit
		} else {
			cleanUnits[*unit.PersNumber] = newestUnit(existingUnit, unit)
		}
	}

	for persNumber, unit := range cleanUnits {
		// Change parent ID to parent persNumber
		if unit.ParentId != nil {
			parentPersNumber := idToPersNum[*unit.ParentId]
			unit.ParentId = &parentPersNumber
		}
		// Clean names
		*unit.Name = strings.Join(strings.Fields(*unit.Name), " ")
		*unit.Name = capitalize(*unit.Name)
		*unit.BriefName = strings.Join(strings.Fields(*unit.BriefName), " ")
		*unit.BriefName = strings.ToUpper(*unit.BriefName)
		cleanUnits[persNumber] = unit
	}

	return cleanUnits
}

func getCleanUnitsCodeMap(config *configparser.Configuration) map[int]asuModels.Unit {
	units := getCleanUnitsPersNumberMap(config)

	codeToUnitsMap := map[int]asuModels.Unit{}

	for _, unit := range units {
		codeToUnitsMap[unitCodeToInt(unit.Code)] = unit
	}

	return codeToUnitsMap
}

func unitCodeToInt(code string) int {
	var numCode int
	code = strings.Replace(code, " ", "", -1)
	numCode, err := strconv.Atoi(code)
	checkErr(err)
	return numCode
}

func newestUnit(unit1 asuModels.Unit, unit2 asuModels.Unit) asuModels.Unit {
	if unit1.CreateOrderDate != nil && unit2.CreateOrderDate != nil {
		if unit1.CreateOrderDate.Unix() > unit2.CreateOrderDate.Unix() {
			return unit1
		}
		return unit2
	}
	return unit1

}

// P E O P L E

func getPeople(config *configparser.Configuration) api11Models.People {
	section, err := config.Section("sd.mephi.ru")
	checkErr(err)

	sdApi11.SetApiKey(section.ValueOf("api11_key"))

	people, err := sdApi11.GetPeople()
	checkErr(err)

	return people
}

func getPeopleMap(config *configparser.Configuration) map[int]api11Models.Person {
	people := getPeople(config)
	return peopleToMap(people)
}

func peopleToMap(people api11Models.People) map[int]api11Models.Person {
	peopleMap := map[int]api11Models.Person{}

	for _, person := range people {
		peopleMap[person.Id] = person
	}

	return peopleMap
}

// R O L E S

func getRoles(config *configparser.Configuration) api12Models.Roles {
	section, err := config.Section("sd.mephi.ru")
	checkErr(err)

	sdApi12.SetApiKey(section.ValueOf("api12_key"))

	heads, err := sdApi12.GetRoles(api12Models.Role{ServiceName: "voip", RoleName: "head"})
	checkErr(err)
	openstack_users, err := sdApi12.GetRoles(api12Models.Role{ServiceName: "voip", RoleName: "openstack_user"})
	checkErr(err)
	openstack_admins, err := sdApi12.GetRoles(api12Models.Role{ServiceName: "voip", RoleName: "openstack_admin"})
	checkErr(err)

	roles := append(heads, openstack_users...)
	roles = append(roles, openstack_admins...)

	return roles
}
