package applicationservice

import (
	"fmt"
	"strconv"
	"strings"

	"bitbucket.org/nemt/nemt-portal-api/application/entitymapping"
	"bitbucket.org/nemt/nemt-portal-api/application/third/npd/npdmodel"
	"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
	"bitbucket.org/nemt/nemt-portal-api/domain/entity"
	"bitbucket.org/nemt/nemt-portal-api/domain/service"
	"github.com/pquerna/ffjson/ffjson"
)

// providerService holds methods to provider application service
type organizationService struct {
	svc       *service.Service
	mapEntity *entitymapping.Mapper
}

// newProviderService returns a providerService instance
func newOrganizationService(svc *service.Service, mapper *entitymapping.Mapper) *organizationService {
	return &organizationService{
		svc:       svc,
		mapEntity: mapper,
	}
}

func (s *organizationService) GetAllTypes() ([]viewmodel.OrganizationType, error) {
	result, err := s.svc.Organization.GetAllTypes()
	if err != nil {
		return nil, err
	}
	return s.mapEntity.Organization.ToOrganizationTypeModelSlice(result), nil
}

func (s *organizationService) GetByType(organizationTypeKey string, user viewmodel.User) ([]viewmodel.Organization, error) {
	userEntity := s.mapEntity.User.ToUserEntity(user)
	result, err := s.svc.Organization.GetByType(organizationTypeKey, userEntity)
	if err != nil {
		return nil, err
	}

	return s.mapEntity.Organization.ToOrganizationModelSlice(result), nil
}

func (s *organizationService) GetByUUID(organizationUUID string, user viewmodel.User) (viewmodel.Organization, error) {
	userEntity := s.mapEntity.User.ToUserEntity(user)
	result, err := s.svc.Organization.GetByUUID(organizationUUID, userEntity)
	if err != nil {
		fmt.Println("Error to get the Organization data by UUID", err)
		return viewmodel.Organization{}, err
	}
	return s.mapEntity.Organization.ToOrganizationModel(result), nil
}

func (s *organizationService) GetByTypeAndReferenceID(typeKey string, referenceID int64, user viewmodel.User) (viewmodel.Organization, error) {
	userEntity := s.mapEntity.User.ToUserEntity(user)
	result, err := s.svc.Organization.GetByTypeAndReferenceID(typeKey, referenceID, userEntity)
	if err != nil {
		return viewmodel.Organization{}, err
	}
	return s.mapEntity.Organization.ToOrganizationModel(result), nil
}

func (s *organizationService) GetByName(name string, searchType string, user viewmodel.User) ([]viewmodel.Organization, error) {
	userEntity := s.mapEntity.User.ToUserEntity(user)
	result, err := s.svc.Organization.GetByName(name, searchType, userEntity)
	if err != nil {
		return nil, err
	}
	return s.mapEntity.Organization.ToOrganizationModelSlice(result), nil
}

func (s *organizationService) SetParentOrganization(organizationUUID string, parentOrganizationUUID string, user viewmodel.User) (viewmodel.Organization, error) {
	userEntity := s.mapEntity.User.ToUserEntity(user)
	child, err := s.svc.Organization.GetByUUID(organizationUUID, userEntity)
	if err != nil {
		return viewmodel.Organization{}, err
	}

	parent, err := s.svc.Organization.GetByUUID(parentOrganizationUUID, userEntity)
	if err != nil {
		return viewmodel.Organization{}, err
	}

	if err := s.svc.Organization.SetParentOrganization(child.ID, parent.ID); err != nil {
		return viewmodel.Organization{}, err
	}

	return s.GetByUUID(organizationUUID, user)
}

func (s *organizationService) InactivateOrganizationAddress(organizationUUID string, address viewmodel.OrganizationAddress, userView viewmodel.User) error {
	userEntity := s.mapEntity.User.ToUserEntity(userView)
	entityAddress := s.mapEntity.Organization.ToOrganizationAddressEntity(address)
	entityAddress.Organization = &entity.Organization{
		UUID: organizationUUID,
	}

	user, err := s.svc.Users.GetByUUID(address.UpdatedUser.ID, "")
	if err != nil {
		return err
	}
	entityAddress.UpdatedUser = user

	if err := s.svc.Organization.InactivateOrganizationAddress(entityAddress, userEntity); err != nil {
		return err
	} else {
		return nil
	}
}

func (s *organizationService) SetOrganizationAddress(organizationUUID string, address viewmodel.OrganizationAddress, userView viewmodel.User) (viewmodel.OrganizationAddress, error) {
	userEntity := s.mapEntity.User.ToUserEntity(userView)
	entityAddress := s.mapEntity.Organization.ToOrganizationAddressEntity(address)
	entityAddress.Organization = &entity.Organization{
		UUID: organizationUUID,
	}

	user, err := s.svc.Users.GetByUUID(address.CreatedUser.ID, "")
	if err != nil {
		return viewmodel.OrganizationAddress{}, err
	}

	entityAddress.CreatedUser = user
	entityAddress.UpdatedUser = user

	entityAddress, err = s.svc.Organization.SetOrganizationAddress(entityAddress, userEntity)
	if err != nil {
		return viewmodel.OrganizationAddress{}, err
	}

	return s.mapEntity.Organization.ToOrganizationAddressModel(entityAddress), nil
}

func (s *organizationService) InactivateOrganizationContact(organizationUUID string, contact viewmodel.OrganizationContact, userView viewmodel.User) error {
	userEntity := s.mapEntity.User.ToUserEntity(userView)
	entityContact := s.mapEntity.Organization.ToOrganizationContactEntity(contact)
	entityContact.Organization = &entity.Organization{
		UUID: organizationUUID,
	}

	user, err := s.svc.Users.GetByUUID(contact.CreatedUser.ID, "")
	if err != nil {
		return err
	}
	entityContact.UpdatedUser = user

	if err := s.svc.Organization.InactivateOrganizationContact(entityContact, userEntity); err != nil {
		return err
	} else {
		return nil
	}
}

func (s *organizationService) SetOrganizationContact(organizationUUID string, contact viewmodel.OrganizationContact, userView viewmodel.User) (viewmodel.OrganizationContact, error) {
	userEntity := s.mapEntity.User.ToUserEntity(userView)
	entityContact := s.mapEntity.Organization.ToOrganizationContactEntity(contact)
	entityContact.Organization = &entity.Organization{
		UUID: organizationUUID,
	}

	user, err := s.svc.Users.GetByUUID(contact.CreatedUser.ID, "")
	if err != nil {
		return viewmodel.OrganizationContact{}, err
	}

	entityContact.CreatedUser = user
	entityContact.UpdatedUser = user

	entityContact, err = s.svc.Organization.SetOrganizationContact(entityContact, userEntity)
	if err != nil {
		return viewmodel.OrganizationContact{}, err
	}

	return s.mapEntity.Organization.ToOrganizationContactModel(entityContact), nil
}

func (s *organizationService) AddOrganization(organization viewmodel.Organization, user viewmodel.User) (viewmodel.Organization, error) {
	enOrg := s.mapEntity.Organization.ToOrganizationEntity(organization)
	enUser := s.mapEntity.User.ToUserEntity(user)

	author, err := s.svc.Users.GetByUUID(organization.Author.ID, "")
	if err != nil {
		return viewmodel.Organization{}, nil
	}
	enOrg.Author = author
	enOrg.LastEditor = enOrg.Author

	if organization.Reference != nil {
		switch organization.Type.Key {
		case "provider":
			var provider npdmodel.ProviderResponse
			bProvider, err := ffjson.Marshal(organization.Reference)
			if err != nil {
				fmt.Println("Error to marshal provider", err)
				return viewmodel.Organization{}, nil
			}
			if err := ffjson.Unmarshal(bProvider, &provider); err != nil {
				fmt.Println("Error to convert provider", err)
				return viewmodel.Organization{}, nil
			}

			eProviders, err := s.svc.Provider.Save(s.mapEntity.Provider.ToProviderEntitySlice([]npdmodel.ProviderResponse{provider}), enUser)
			if err != nil {
				fmt.Println("Error to save provider", err)
				return viewmodel.Organization{}, nil
			}
			respProvider := eProviders[0]
			enOrg.ReferenceID = respProvider.ProviderID

			lat, _ := strconv.ParseFloat(provider.Latitude, 64)
			long, _ := strconv.ParseFloat(provider.Longitude, 64)
			if lat != 0 && long != 0 && len(provider.StreetName1) > 0 {
				address := entity.OrganizationAddress{
					InternalID:  provider.MukID,
					Name:        "Main Address",
					Address:     fmt.Sprintf("%s %s - %s, %s (%s)", provider.StreetName1, provider.StreetName2, provider.CityName, provider.State, provider.ZipCode[:5]),
					Latitude:    lat,
					Longitude:   long,
					CreatedUser: enOrg.Author,
					UpdatedUser: enOrg.LastEditor,
				}
				enOrg.Addresses = append(enOrg.Addresses, address)
			}

			if provider.PhoneNumber != "" {
				formattedPhone := "+1" + strings.Replace(provider.PhoneNumber, "-", "", -1)
				contact := entity.OrganizationContact{
					Type: entity.ContactType{
						Key: "phone",
					},
					Contact:     formattedPhone,
					Name:        "Main Phone",
					CreatedUser: enOrg.Author,
					UpdatedUser: enOrg.LastEditor,
				}
				enOrg.Contacts = append(enOrg.Contacts, contact)
			}
		}
	}

	enOrg, err = s.svc.Organization.AddOrganization(enOrg, enUser)
	if err != nil {
		fmt.Println("Error to save organization on DB", err)
		return viewmodel.Organization{}, nil
	}

	return s.mapEntity.Organization.ToOrganizationModel(enOrg), nil
}
