package organizationroute

import (
	"sync"

	"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
	"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
	"bitbucket.org/nemt/nemt-portal-api/domain"
	"bitbucket.org/nemt/nemt-portal-api/infra/auth"
	"bitbucket.org/nemt/nemt-portal-api/infra/cache"
	"bitbucket.org/nemt/nemt-portal-api/infra/config"
	"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
	"bitbucket.org/nemt/nemt-portal-api/server/authorization"
	"github.com/labstack/echo"
)

var (
	instance *controller
	once     sync.Once
)

type controller struct {
	cfg *config.Config
	svc *applicationservice.Service
}

func controllerInstance(cfg *config.Config, svc *applicationservice.Service) *controller {
	once.Do(func() {
		instance = &controller{
			cfg: cfg,
			svc: svc,
		}
	})

	return instance
}

func (c *controller) handleTypes(ctx echo.Context) error {
	cache := cache.Instance(c.cfg)
	cacheKey := ctx.Request().Method + ctx.Request().URL.EscapedPath() + ctx.Request().URL.RawQuery

	resp := []viewmodel.OrganizationType{}
	err := cache.GetStruct(cacheKey, &resp)
	if err != nil {
		if err != domain.ErrCacheMiss {
			ctx.Logger().Errorf(domain.LogProblemGettingFromCache, err)
		}
		resp, err = c.svc.Organization.GetAllTypes()
		if err != nil {
			return routeutils.HandleAPIError(ctx, err)
		}
		cache.SetStruct(cacheKey, resp)
	}

	return routeutils.ResponseAPIOK(ctx, resp)
}

func (c *controller) handleAddOrganization(ctx echo.Context) error {
	var org viewmodel.Organization
	err := ctx.Bind(&org)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}
	authUser, err := auth.GetUserDetail(ctx, c.cfg)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	if !authorization.CanCreateOrganization(authUser, org) {
		return routeutils.ResponseAPIAuthorizationError(ctx)
	}

	org.Author.ID = authUser.ID
	org.LastEditor.ID = authUser.ID

	resp, err := c.svc.Organization.AddOrganization(org, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	return routeutils.ResponseAPIOK(ctx, resp)
}

func (c *controller) handle(ctx echo.Context) error {
	orgType, _ := routeutils.GetAndValidateStringQueryParam(ctx, "type", "Type is mandatory")

	authUser, err := auth.GetUserDetail(ctx, c.cfg)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	resp, err := c.svc.Organization.GetByType(orgType, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	return routeutils.ResponseAPIOK(ctx, resp)
}

func (c *controller) handleDetail(ctx echo.Context) error {
	orgUUID, err := routeutils.GetAndValidateStringParam(ctx, "org_uuid", "Org ID is mandatory")
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	authUser, err := auth.GetUserDetail(ctx, c.cfg)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	resp, err := c.svc.Organization.GetByUUID(orgUUID, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	return routeutils.ResponseAPIOK(ctx, resp)
}

func (c *controller) handleParent(ctx echo.Context) error {
	var parent viewmodel.Organization
	err := ctx.Bind(&parent)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	orgUUID, err := routeutils.GetAndValidateStringParam(ctx, "org_uuid", "Org ID is mandatory")
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	authUser, err := auth.GetUserDetail(ctx, c.cfg)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	organization, err := c.svc.Organization.GetByUUID(orgUUID, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	if !authorization.CanUpdateOrganization(authUser, organization){
		return routeutils.ResponseAPIAuthorizationError(ctx)
	}

	resp, err := c.svc.Organization.SetParentOrganization(orgUUID, parent.UUID, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	return routeutils.ResponseAPIOK(ctx, resp)
}

func (c *controller) handleChild(ctx echo.Context) error {
	var child viewmodel.Organization
	err := ctx.Bind(&child)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	orgUUID, err := routeutils.GetAndValidateStringParam(ctx, "org_uuid", "Org ID is mandatory")
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	authUser, err := auth.GetUserDetail(ctx, c.cfg)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	organization, err := c.svc.Organization.GetByUUID(orgUUID, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	if !authorization.CanUpdateOrganization(authUser, organization){
		return routeutils.ResponseAPIAuthorizationError(ctx)
	}

	_, err = c.svc.Organization.SetParentOrganization(child.UUID, orgUUID, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	resp, err := c.svc.Organization.GetByUUID(orgUUID, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	return routeutils.ResponseAPIOK(ctx, resp)
}

func (c *controller) handleNameSearch(ctx echo.Context) error {
	name, err := routeutils.GetAndValidateStringQueryParam(ctx, "name", "Name is mandatory")
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	authUser, err := auth.GetUserDetail(ctx, c.cfg)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	searchType := ""
	searchType, _ = routeutils.GetAndValidateStringQueryParam(ctx, "type", "Type is mandatory")

	cache := cache.Instance(c.cfg)
	cacheKey := ctx.Request().Method + ctx.Request().URL.RawPath + ctx.Request().URL.RawQuery + authUser.ID

	resp := []viewmodel.Organization{}
	err = cache.GetStruct(cacheKey, &resp)
	if err != nil {
		if err != domain.ErrCacheMiss {
			ctx.Logger().Errorf(domain.LogProblemGettingFromCache, err)
		}
		resp, err = c.svc.Organization.GetByName(name, searchType, authUser)
		if err != nil {
			return routeutils.HandleAPIError(ctx, err)
		}
		cache.SetStruct(cacheKey, resp)
	}

	return routeutils.ResponseAPIOK(ctx, resp)
}

func (c *controller) handleRemoveAddress(ctx echo.Context) error {
	var address viewmodel.OrganizationAddress
	err := ctx.Bind(&address)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	authUser, err := auth.GetUserDetail(ctx, c.cfg)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	orgUUID, err := routeutils.GetAndValidateStringParam(ctx, "org_uuid", "Org ID is mandatory")
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	address.UpdatedUser.ID = authUser.ID

	err = c.svc.Organization.InactivateOrganizationAddress(orgUUID, address, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	resp, err := c.svc.Organization.GetByUUID(orgUUID, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	return routeutils.ResponseAPIOK(ctx, resp)
}

func (c *controller) handleAddAddress(ctx echo.Context) error {
	var address viewmodel.OrganizationAddress
	err := ctx.Bind(&address)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	orgUUID, err := routeutils.GetAndValidateStringParam(ctx, "org_uuid", "Org ID is mandatory")
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	authUser, err := auth.GetUserDetail(ctx, c.cfg)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	organization, err := c.svc.Organization.GetByUUID(orgUUID, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	if !authorization.CanCreateAddress(authUser, organization) {
		return routeutils.ResponseAPIAuthorizationError(ctx)
	}

	return routeutils.ResponseAPIAuthorizationError(ctx)

	address.CreatedUser.ID = authUser.ID
	address.UpdatedUser.ID = authUser.ID

	_, err = c.svc.Organization.SetOrganizationAddress(orgUUID, address, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	resp, err := c.svc.Organization.GetByUUID(orgUUID, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	return routeutils.ResponseAPIOK(ctx, resp)
}

func (c *controller) handleRemoveContact(ctx echo.Context) error {
	var contact viewmodel.OrganizationContact
	err := ctx.Bind(&contact)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	orgUUID, err := routeutils.GetAndValidateStringParam(ctx, "org_uuid", "Org ID is mandatory")
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	authUser, err := auth.GetUserDetail(ctx, c.cfg)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	contact.UpdatedUser.ID = authUser.ID

	err = c.svc.Organization.InactivateOrganizationContact(orgUUID, contact, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	resp, err := c.svc.Organization.GetByUUID(orgUUID, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	return routeutils.ResponseAPIOK(ctx, resp)
}

func (c *controller) handleAddContact(ctx echo.Context) error {
	var contact viewmodel.OrganizationContact
	err := ctx.Bind(&contact)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	orgUUID, err := routeutils.GetAndValidateStringParam(ctx, "org_uuid", "Org ID is mandatory")
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	authUser, err := auth.GetUserDetail(ctx, c.cfg)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	organization, err := c.svc.Organization.GetByUUID(orgUUID, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	if !authorization.CanCreateContact(authUser, organization) {
		return routeutils.ResponseAPIAuthorizationError(ctx)
	}

	contact.CreatedUser.ID = authUser.ID
	contact.UpdatedUser.ID = authUser.ID

	_, err = c.svc.Organization.SetOrganizationContact(orgUUID, contact, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	resp, err := c.svc.Organization.GetByUUID(orgUUID, authUser)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	return routeutils.ResponseAPIOK(ctx, resp)
}
