@FeignClient(name = "business-process-service", url = "${nimble.business-process-service.url}", fallback = BusinessProcessServiceClientFallback.class)
public interface BusinessProcessServiceClient {

    @RequestMapping(method = RequestMethod.GET, value = "/collaboration-groups/all-finished")
    public String checkAllCollaborationsFinished(@RequestParam(value = "partyId", required = true) String partyId,
                                                 @RequestHeader(value = "federationId", required = true) String federationId,
                                                 @RequestHeader(value = "Authorization", required = true) String bearerToken);

} @FeignClient(name = "catalogue-service", url = "${nimble.catalogue-service.url}", fallback = CatalogueServiceClientFallback.class)
public interface CatalogueServiceClient {

    @RequestMapping(method = RequestMethod.POST, value = "/admin/index-catalogues")
    void indexAllCatalogues(@RequestParam(value = "partyId") String partyId,
                            @RequestHeader(value = "Authorization", required = true) String bearerToken);

} @FeignClient(name = "indexing-service", fallback = IndexingClientFallback.class)
public interface IndexingClient {
    @RequestMapping(method = RequestMethod.PUT, value = "/party", consumes = "application/json")
    @Headers("Content-Type: application/json")
    Response setParty(@RequestBody PartyType party,
                      @RequestHeader(value = "Authorization", required = true) String bearerToken);

    @RequestMapping(method = RequestMethod.GET, value = "/party")
    PartyType getParty(@RequestParam(value = "uri") String uri,
    @RequestHeader(value = "Authorization", required = true) String bearerToken);

    @RequestMapping(method = RequestMethod.DELETE, value = "/party")
    Boolean deleteParty(@RequestParam(value = "uri") String uri,
    @RequestHeader(value = "Authorization", required = true) String bearerToken);

    @RequestMapping(method = RequestMethod.POST, value = "/item/search", consumes = "application/json")
    SearchResult searchItem(@RequestBody Search search,
            @RequestHeader(value = "Authorization", required = true) String bearerToken);

    @RequestMapping(method = RequestMethod.DELETE, value = "/item")
    Boolean removeItem(@RequestParam(value = "uri") String uri,
            @RequestHeader(value = "Authorization", required = true) String bearerToken);

    @RequestMapping(method = RequestMethod.DELETE, value = "/catalogue")
    Boolean deleteCatalogue(@RequestParam(value = "uri") String uri,
            @RequestHeader(value = "Authorization", required = true) String bearerToken);

    @RequestMapping(method = RequestMethod.POST, value = "/class/search", consumes = "application/json")
    SearchResult searchClass(
            @RequestBody Search search,
            @RequestHeader(value = "Authorization") String bearerToken);
} public class GlobalConstants {

    public static final String PARTY_NAME_STRING = "partyName";
    public static final String ASCENDING_STRING = "asc";
    public static final String DESCENDING_STRING = "desc";
    public static final String JWT_SUBJECT_ATTRIBUTE_STRING = "sub";
    public static final String JWT_EXPIRY_ATTRIBUTE_STRING = "exp";
    public static final String JWT_TYPE_ATTRIBUTE_STRING = "type";
    public static final String EMAIL_STRING = "email";
    public static final String USER_NAME_STRING = "username";

    // OAUTH Related Constants
    public static final String CLIENT_CREDENTIALS_FLOW = "client_credentials";
    public static final String AUTHORIZATION_CODE_FLOW = "authorization_code";
    public static final String REFRESH_TOKEN_FLOW = "refresh_token";

} @Entity
@JsonInclude
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"id", "company_hjid"})})
public class NegotiationSettings implements Serializable {

    @Id
    @JsonIgnore
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToOne(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
    @JoinColumn(unique = true)
    private PartyType company;

    @JoinTable
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Range> deliveryPeriodRanges = new ArrayList<>();

    @ElementCollection(targetClass = String.class)
    private List<String> deliveryPeriodUnits = new ArrayList<>();

    @JoinTable
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Range> warrantyPeriodRanges = new ArrayList<>();

    @ElementCollection(targetClass = String.class)
    private List<String> warrantyPeriodUnits = new ArrayList<>();

    @ElementCollection(targetClass = String.class)
    private List<String> incoterms = new ArrayList<>();

    @ElementCollection(targetClass = String.class)
    private List<String> paymentTerms = new ArrayList<>();

    @ElementCollection(targetClass = String.class)
    private List<String> paymentMeans = new ArrayList<>();

    private String serviceLevel;

    @JoinTable
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private List<CompanySensor> sensors = new ArrayList<>();


    public NegotiationSettings(PartyType company, List<Range> deliveryPeriodRanges, List<String> deliveryPeriodUnits, List<Range> warrantyPeriodRange,
                               List<String> warrantyPeriodUnits, List<String> incoterms, List<String> paymentTerms, List<String> paymentMeans,
                               List<CompanySensor> sensors, String serviceLevel) {
        this.company = company;
        this.deliveryPeriodRanges = deliveryPeriodRanges;
        this.deliveryPeriodUnits = deliveryPeriodUnits;
        this.warrantyPeriodRanges = warrantyPeriodRange;
        this.warrantyPeriodUnits = warrantyPeriodUnits;
        this.incoterms = incoterms;
        this.paymentTerms = paymentTerms;
        this.paymentMeans = paymentMeans;
        this.serviceLevel = serviceLevel;
        this.sensors = sensors;
    }

    public NegotiationSettings() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public PartyType getCompany() {
        return company;
    }

    public void setCompany(PartyType company) {
        this.company = company;
    }

    public List<Range> getDeliveryPeriodRanges() {
        return deliveryPeriodRanges;
    }

    public void setDeliveryPeriodRanges(List<Range> deliveryPeriodRanges) {
        this.deliveryPeriodRanges = deliveryPeriodRanges;
    }

    public List<String> getDeliveryPeriodUnits() {
        return deliveryPeriodUnits;
    }

    public void setDeliveryPeriodUnits(List<String> deliveryPeriodUnits) {
        this.deliveryPeriodUnits = deliveryPeriodUnits;
    }

    public List<Range> getWarrantyPeriodRanges() {
        return warrantyPeriodRanges;
    }

    public void setWarrantyPeriodRanges(List<Range> warrantyPeriodRanges) {
        this.warrantyPeriodRanges = warrantyPeriodRanges;
    }

    public List<String> getWarrantyPeriodUnits() {
        return warrantyPeriodUnits;
    }

    public void setWarrantyPeriodUnits(List<String> warrantyPeriodUnits) {
        this.warrantyPeriodUnits = warrantyPeriodUnits;
    }

    public List<String> getIncoterms() {
        return incoterms;
    }

    public void setIncoterms(List<String> incoterms) {
        this.incoterms = incoterms;
    }

    public List<String> getPaymentTerms() {
        return paymentTerms;
    }

    public void setPaymentTerms(List<String> paymentTerms) {
        this.paymentTerms = paymentTerms;
    }

    public List<String> getPaymentMeans() {
        return paymentMeans;
    }

    public void setPaymentMeans(List<String> paymentMeans) {
        this.paymentMeans = paymentMeans;
    }

    public List<CompanySensor> getSensors() {
        return sensors;
    }

    public void setSensors(List<CompanySensor> sensors) {
        this.sensors = sensors;
    }

    public String getServiceLevel() {
        return serviceLevel;
    }

    public void setServiceLevel(String serviceLevel) {
        this.serviceLevel = serviceLevel;
    }

    public void update(NegotiationSettings newSettings) {
        this.deliveryPeriodRanges.clear();
        this.deliveryPeriodRanges.addAll(newSettings.getDeliveryPeriodRanges());

        this.deliveryPeriodUnits.clear();
        this.deliveryPeriodUnits.addAll(newSettings.getDeliveryPeriodUnits());

        this.warrantyPeriodRanges.clear();
        this.warrantyPeriodRanges.addAll(newSettings.getWarrantyPeriodRanges());

        this.warrantyPeriodUnits.clear();
        this.warrantyPeriodUnits.addAll(newSettings.getWarrantyPeriodUnits());

        this.incoterms.clear();
        this.incoterms.addAll(newSettings.getIncoterms());

        this.paymentMeans.clear();
        this.paymentMeans.addAll(newSettings.getPaymentMeans());

        this.paymentTerms.clear();
        this.paymentTerms.addAll(newSettings.getPaymentTerms());

        if (null != newSettings.company) {
            this.company.setSalesTerms(newSettings.company.getSalesTerms());
            this.company.getProcessIDItems().clear();
            this.company.setProcessID(newSettings.company.getProcessID());

            this.company.getNetwork().clear();
            this.company.getNetwork().addAll(newSettings.company.getNetwork());
        }
    }

    @Entity
    public static class CompanySensor {

        @Id
        @JsonIgnore
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

        @Column
        @JsonProperty("machine")
        @SerializedName("machine")
        private String machine;

        @Column
        @JsonProperty("sensor")
        @SerializedName("sensor")
        private String sensor;

        @Column
        @JsonProperty("format")
        @SerializedName("format")
        private String format;
    }

    @Entity
    public static class Range {

        @Id
        @JsonIgnore
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

        @Column
        @JsonProperty("start")
        @SerializedName("start")
        private Long rangeStart;

        @Column
        @JsonProperty("end")
        @SerializedName("end")
        private Long rangeEnd;

        public Range(long start, long end) {
            this.rangeStart = start;
            this.rangeEnd = end;
        }

        public Range() {
        }

        public long getRangeStart() {
            return rangeStart;
        }

        public long getRangeEnd() {
            return rangeEnd;
        }
    }
} @Entity
public class UaaUser implements Serializable {

    @Id
    @Column(nullable = false, unique = true)
    private String externalID;

    private String username;

    @OneToOne
    private PersonType ublPerson;

    @ColumnDefault("false")
    private Boolean showWelcomeInfo = false;

    public UaaUser() {
    }

    public UaaUser(String username, PersonType ublPerson, String externalID) {
        this.username = username;
        this.ublPerson = ublPerson;
        this.externalID = externalID;
        this.showWelcomeInfo = true;
    }

    public String getUsername() {
        return username;
    }

    public String getExternalID() {
        return externalID;
    }

    public PersonType getUBLPerson() {
        return this.ublPerson;
    }

    public Boolean getShowWelcomeInfo() {
        return showWelcomeInfo;
    }

    public void setShowWelcomeInfo(Boolean showWelcomeInfo) {
        this.showWelcomeInfo = showWelcomeInfo;
    }
} @Entity
@Table(uniqueConstraints = {
        @UniqueConstraint(columnNames = {"email", "companyId"})
})
public class UserInvitation {

    @Id
    @JsonIgnore
    @GeneratedValue
    long id;

    @Email
    @NotBlank
    @JsonProperty(required = true)
    private String email;

    @NotBlank
    @JsonProperty(required = true)
    private String companyId;

    @Column
    @ElementCollection(targetClass = String.class)
    private List<String> roleIDs;

    @ManyToOne
    @JsonIgnore
    private UaaUser sender;

    private Boolean isPending;

    protected UserInvitation() {
        // no-args constructor required by JPA spec
        // this one is protected since it shouldn't be used directly
    }

    public UserInvitation(String email, String companyId, List<String> roleIDs, UaaUser sender) {
        this(email, companyId, roleIDs, sender, true);
    }

    public UserInvitation(String email, String companyId, List<String> roleIDs, UaaUser sender, Boolean isPending) {
        this.email = email;
        this.companyId = companyId;
        this.sender = sender;
        this.roleIDs = roleIDs;

        this.isPending = isPending;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getCompanyId() {
        return companyId;
    }

    public void setCompanyId(String companyId) {
        this.companyId = companyId;
    }

    public List<String> getRoleIDs() {
        return roleIDs;
    }

    public void setRoleIDs(List<String> roleIDs) {
        this.roleIDs = roleIDs;
    }

    public UaaUser getSender() {
        return sender;
    }

    public void setSender(UaaUser sender) {
        this.sender = sender;
    }

    public Boolean getPending() {
        return isPending;
    }

    public void setPending(Boolean pending) {
        isPending = pending;
    }
} @Service
@SuppressWarnings("Duplicates")
public class EmailService {

    private static final Logger logger = LoggerFactory.getLogger(EmailService.class);

    @Autowired
    private JavaMailSender emailSender;

    @Autowired
    private UblUtils ublUtils;

    @Autowired
    private MessageSource messageSource;

    @Autowired
    private TemplateEngine textMailTemplateEngine;

    @Value("${spring.mail.defaultFrom}")
    private String defaultFrom;

    @Value("${spring.mail.debug:false}")
    private Boolean debug;

    @Value("${nimble.frontend.url}")
    private String frontendUrl;

    @Value("${nimble.supportEmail}")
    private String supportEmail;

    @Value("${nimble.platformName}")
    private String platformVersion;

    @Value("${spring.mail.platformName}")
    private String platformName;

    @Value("${nimble.frontend.registration.url}")
    private String frontendRegistrationUrl;

    @Value("${nimble.frontend.company-details.url}")
    private String companyDetailsUrl;

    @Value("${spring.mail.languages}")
    private String mailTemplateLanguages;

    @Value("${nimble.companyDataUpdateEmail}")
    private String companyDataUpdateEmail;

    public void sendResetCredentialsLink(String toEmail, String credentials, String language) throws UnsupportedEncodingException{
        String resetCredentialsURL = frontendUrl + "/#/user-mgmt/forgot/?key=" + URLEncoder.encode(credentials, "UTF-8");
        Context context = new Context();
        context.setVariable("resetPasswordURL", resetCredentialsURL);
        context.setVariable("platformName",platformName);

        String version = Strings.isNullOrEmpty(platformVersion) ? "": String.format(" (%s)",platformVersion);
        String subject = getMailSubject(NimbleMessageCode.MAIL_SUBJECT_RESET_CREDENTIALS_LINK,language,Arrays.asList(platformName,version));

        this.send(new String[]{toEmail}, subject, getTemplateName("password-reset",language), context);
    }

    public void sendInvite(String toEmail, String senderName, String companyName, Collection<String> roles, String language) throws UnsupportedEncodingException {
        String invitationUrl = String.format("%s/%s/?email=%s",frontendUrl,frontendRegistrationUrl,URLEncoder.encode(toEmail, "UTF-8"));

        Context context = new Context();
        context.setVariable("senderName", senderName);
        context.setVariable("companyName", companyName);
        context.setVariable("invitationUrl", invitationUrl);
        context.setVariable("roles", roles);
        context.setVariable("platformName",platformName);

        String version = Strings.isNullOrEmpty(platformVersion) ? "": String.format(" (%s)",platformVersion);
        String subject = getMailSubject(NimbleMessageCode.MAIL_SUBJECT_INVITATION,language,Arrays.asList(platformName,version));

        this.send(new String[]{toEmail}, subject, getTemplateName("invitation",language), context);
    }

    public void sendSubscriptionSummary(List<String> toEmail, List<SubscriptionSummary> subscriptionSummaryList, String language) throws UnsupportedEncodingException {
        Context context = new Context();

        // keeps the list of SubscriptionMailModel which corresponds to the 'subscriptions' variable in mail template
        List<SubscriptionMailModel> subscriptionMailModels = new ArrayList<>();
        // populate subscriptionMailModels array
        for (SubscriptionSummary subscriptionSummary : subscriptionSummaryList) {
            // retrieve the urls for product details
            List<String> productDetailsUrls = new ArrayList<>();
            for (int i = 0; i < subscriptionSummary.getCatalogueIds().size(); i++) {
                productDetailsUrls.add(String.format("%s/#/product-details?catalogueId=%s&id=%s",frontendUrl,URLEncoder.encode(subscriptionSummary.getCatalogueIds().get(i), "UTF-8"),
                        URLEncoder.encode(subscriptionSummary.getProductIds().get(i), "UTF-8")));
            }
            // set title of subscription
            String title;
            // company
            if(subscriptionSummary.getCompanyName() != null){
                title = String.format("The following products are published/updated by %s",subscriptionSummary.getCompanyName());
            }
            // category
            else{
                title = String.format("The following products are published/updated on category: %s",subscriptionSummary.getCategoryName());
            }
            // create the subscription mail model
            SubscriptionMailModel subscriptionMailModel = new SubscriptionMailModel();
            subscriptionMailModel.setTitle(title);
            subscriptionMailModel.setProductUrls(productDetailsUrls);

            subscriptionMailModels.add(subscriptionMailModel);
        }
        // set context variables
        context.setVariable("subscriptions", subscriptionMailModels);
        context.setVariable("platformName",platformName);
        // set mail subject
        String version = Strings.isNullOrEmpty(platformVersion) ? "": String.format(" (%s)",platformVersion);
        String subject = getMailSubject(NimbleMessageCode.MAIL_SUBJECT_SUBSCRIPTION,language,Arrays.asList(platformName,version));

        this.send(toEmail.toArray(new String[0]), subject, getTemplateName("subscription",language), context);
    }

    public void informInviteExistingCompany(String toEmail, String senderName, String companyName, Collection<String> roles, String language) {
        Context context = new Context();
        context.setVariable("senderName", senderName);
        context.setVariable("companyName", companyName);
        context.setVariable("nimbleUrl", frontendUrl);
        context.setVariable("roles", roles);
        context.setVariable("platformName",platformName);

        String version = Strings.isNullOrEmpty(platformVersion) ? "": String.format(" (%s)",platformVersion);
        String subject = getMailSubject(NimbleMessageCode.MAIL_SUBJECT_INVITATION_EXISTING_COMPANY, language, Arrays.asList(companyName,platformName,version));

        this.send(new String[]{toEmail}, subject, getTemplateName("invitation_existing_company",language), context);
    }

    public void notifyPlatformManagersCompanyDataUpdates(List<String> emails, PersonType user, PartyType company, CompanyDetailsUpdates companyDetailsUpdates, String language){
        Context context = new Context();

        // collect info of user
        String username = user.getFirstName() + " " + user.getFamilyName();
        context.setVariable("username", username);

        // collect info of company and platform
        context.setVariable("companyName", ublUtils.getName(company));
        context.setVariable("platformName",platformName);
        // company updates
        context.setVariable("companyUpdates",getMailContentForCompanyDetailsUpdates(companyDetailsUpdates,language));
        // link to the company details
        context.setVariable("companyDetailsUrl", String.format("%s/%s?id=%s&viewMode=mgmt",frontendUrl,companyDetailsUrl,company.getPartyIdentification().get(0).getID()));

        String version = Strings.isNullOrEmpty(platformVersion) ? "": String.format(" (%s)",platformVersion);
        String subject = getMailSubject(NimbleMessageCode.MAIL_SUBJECT_COMPANY_DATA_UPDATED, language, Arrays.asList(platformName,version));

        // if the specific email address is defined for the company data updates, use it to send the email
        // otherwise, send the email to all platform managers
        if(!Strings.isNullOrEmpty(this.companyDataUpdateEmail)){
            emails = Arrays.asList(this.companyDataUpdateEmail);
        }

        this.send(emails.toArray(new String[]{}), subject, getTemplateName("company_data_updated",language), context);
    }

    public void notifyPlatformManagersNewCompany(List<String> emails, PersonType representative, PartyType company, String language) {

        Context context = new Context();

        // collect info of user
        String username = representative.getFirstName() + " " + representative.getFamilyName();
        context.setVariable("username", username);

        String userEmail = null;
        if (representative.getContact() != null)
            userEmail = representative.getContact().getElectronicMail();
        context.setVariable("userEmail", userEmail);

        // collect info of user
        context.setVariable("companyName", ublUtils.getName(company));
        context.setVariable("companyID", company.getHjid());
        context.setVariable("platformName",platformName);

        // collect info of company
        if (company.getPostalAddress() != null) {
            AddressType address = company.getPostalAddress();
            String countryName = address.getCountry() != null && address.getCountry().getIdentificationCode() != null? CountryUtil.getCountryNameByISOCode(address.getCountry().getIdentificationCode().getValue()) : null;
            context.setVariable("companyCountry", countryName);
            context.setVariable("companyStreet", address.getStreetName());
            context.setVariable("companyBuildingNumber", address.getBuildingNumber());
            context.setVariable("companyCity", address.getCityName());
            context.setVariable("companypostalCode", address.getPostalZone());
        }

        String version = Strings.isNullOrEmpty(platformVersion) ? "": String.format(" (%s)",platformVersion);
        String subject = getMailSubject(NimbleMessageCode.MAIL_SUBJECT_COMPANY_REGISTERED, language, Arrays.asList(platformName,version));

        this.send(emails.toArray(new String[]{}), subject, getTemplateName("new_company",language), context);
    }

    public void notifyVerifiedCompany(String email, PersonType legalRepresentative, PartyType company, String language) {

        Context context = new Context();
        context.setVariable("firstName", legalRepresentative.getFirstName());
        context.setVariable("familyName", legalRepresentative.getFamilyName());
        context.setVariable("companyName", ublUtils.getName(company));
        context.setVariable("supportEmail", supportEmail);
        context.setVariable("nimbleUrl", frontendUrl);
        context.setVariable("platformName",platformName);

        String version = Strings.isNullOrEmpty(platformVersion) ? "": String.format(" (%s)",platformVersion);
        String subject = getMailSubject(NimbleMessageCode.MAIL_SUBJECT_COMPANY_VERIFIED,language,Arrays.asList(platformName,version));

        this.send(new String[]{email}, subject, getTemplateName("company_verified",language), context);
    }

    public void notifyDeletedCompany(List<PersonType> legalRepresentatives, PartyType company, String language) {

        String companyName = ublUtils.getName(company);
        for (PersonType legalRepresentative : legalRepresentatives) {
            Context context = new Context();
            context.setVariable("firstName", legalRepresentative.getFirstName());
            context.setVariable("familyName", legalRepresentative.getFamilyName());
            context.setVariable("companyName", companyName);
            context.setVariable("platformName",platformName);

            String version = Strings.isNullOrEmpty(platformVersion) ? "": String.format(" (%s)",platformVersion);
            String subject = getMailSubject(NimbleMessageCode.MAIL_SUBJECT_COMPANY_DELETED,language,Arrays.asList(platformName,version));

            try {
                this.send(new String[]{legalRepresentative.getContact().getElectronicMail()}, subject, getTemplateName("company_deleted",language), context);
            }catch (Exception e){
                logger.error("Failed to send email:",e);
            }
        }
    }

    private void send(String[] to, String subject, String template, Context context) {

        SimpleMailMessage mailMessage = new SimpleMailMessage();
        String message = this.textMailTemplateEngine.process(template, context);

        if (debug) {
            logger.info(message);
            return;
        }

        mailMessage.setFrom(this.defaultFrom);
        mailMessage.setTo(to);
        mailMessage.setSubject(subject);
        mailMessage.setText(message);

        this.emailSender.send(mailMessage);
    }

    private String getTemplateName(String templateName,String language){
        List<String> languages = Arrays.asList(mailTemplateLanguages.split(","));
        if(languages.contains(language)){
            return String.format("%s_%s",templateName,language);
        }
        return String.format("%s_%s",templateName,languages.get(0));
    }

    private String getMailSubject(NimbleMessageCode messageCode, String language, List<String> parameters){
        return this.messageSource.getMessage(messageCode.toString(), parameters.toArray(), getLocale(language));
    }

    /**
     * Returns the {{@link Locale}} for the given language
     * */
    private Locale getLocale(String language){
        List<String> languages = Arrays.asList(mailTemplateLanguages.split(","));
        String mailSubjectLanguage = languages.contains(language) ? language :languages.get(0) ;
        return new Locale(mailSubjectLanguage);
    }

    /**
     * Returns the mail content for company details updates
     * @param companyDetailsUpdates the updated fields of company details
     * @param language the language id to be used to put proper translations of company fields
     * @return the mail content for company details updates as string
     * */
    private String getMailContentForCompanyDetailsUpdates(CompanyDetailsUpdates companyDetailsUpdates,String language){
        Locale locale = getLocale(language);

        StringBuilder content = new StringBuilder();
        // company name
        if(companyDetailsUpdates.getLegalName() != null){
            content.append(this.messageSource.getMessage(NimbleMessageCode.COMPANY_NAME.toString(),null,locale)).append(":\n");
            content.append(stringifyLanguageMap(companyDetailsUpdates.getLegalName())).append("\n");
        }
        // brand name
        if(companyDetailsUpdates.getBrandName() != null){
            content.append(this.messageSource.getMessage(NimbleMessageCode.BRAND_NAME.toString(),null,locale)).append(":\n");
            content.append(stringifyLanguageMap(companyDetailsUpdates.getBrandName())).append("\n");
        }
        // vat number
        if(companyDetailsUpdates.getVatNumber() != null){
            content.append(this.messageSource.getMessage(NimbleMessageCode.VAT_NUMBER.toString(),null,locale)).append(":\n");
            content.append(companyDetailsUpdates.getVatNumber()).append("\n\n");
        }
        // verification info
        if(companyDetailsUpdates.getVerificationInformation() != null){
            content.append(this.messageSource.getMessage(NimbleMessageCode.VERIFICATION_INFO.toString(),null,locale)).append(":\n");
            content.append(companyDetailsUpdates.getVerificationInformation()).append("\n\n");
        }
        // business type
        if(companyDetailsUpdates.getBusinessType() != null){
            String businessTypeKey = companyDetailsUpdates.getBusinessType().replace(" ","_").toUpperCase();
            content.append(this.messageSource.getMessage(NimbleMessageCode.BUSINESS_TYPE.toString(),null,locale)).append(":\n");
            content.append(this.messageSource.getMessage(businessTypeKey,null,locale)).append("\n\n");
        }
        // activity sectors
        if(companyDetailsUpdates.getIndustrySectors() != null){
            content.append(this.messageSource.getMessage(NimbleMessageCode.ACTIVITY_SECTORS.toString(),null,locale)).append(":\n");
            content.append(stringifyLanguageMap(companyDetailsUpdates.getIndustrySectors())).append("\n");
        }
        // business keywords
        if(companyDetailsUpdates.getBusinessKeywords() != null){
            content.append(this.messageSource.getMessage(NimbleMessageCode.BUSINESS_KEYWORDS.toString(),null,locale)).append(":\n");
            content.append(stringifyLanguageMap(companyDetailsUpdates.getBusinessKeywords())).append("\n");
        }
        // year of foundation
        if(companyDetailsUpdates.getYearOfCompanyRegistration() != null){
            content.append(this.messageSource.getMessage(NimbleMessageCode.YEAR_FOUNDATION.toString(),null,locale)).append(":\n");
            content.append(companyDetailsUpdates.getYearOfCompanyRegistration()).append("\n\n");
        }
        // address
        if(companyDetailsUpdates.getAddress() != null){
            // street
            content.append(this.messageSource.getMessage(NimbleMessageCode.STREET.toString(),null,locale)).append(":\n");
            content.append(companyDetailsUpdates.getAddress().getStreetName()).append("\n\n");
            // building number
            content.append(this.messageSource.getMessage(NimbleMessageCode.BUILDING_NUMBER.toString(),null,locale)).append(":\n");
            content.append(companyDetailsUpdates.getAddress().getBuildingNumber()).append("\n\n");
            // city/town
            content.append(this.messageSource.getMessage(NimbleMessageCode.CITY_TOWN.toString(),null,locale)).append(":\n");
            content.append(companyDetailsUpdates.getAddress().getCityName()).append("\n\n");
            // state/province
            content.append(this.messageSource.getMessage(NimbleMessageCode.STATE_PROVINCE.toString(),null,locale)).append(":\n");
            content.append(companyDetailsUpdates.getAddress().getRegion()).append("\n\n");
            // postal code
            content.append(this.messageSource.getMessage(NimbleMessageCode.POSTAL_CODE.toString(),null,locale)).append(":\n");
            content.append(companyDetailsUpdates.getAddress().getPostalCode()).append("\n\n");
            // country
            content.append(this.messageSource.getMessage(NimbleMessageCode.COUNTRY.toString(),null,locale)).append(":\n");
            content.append(companyDetailsUpdates.getAddress().getCountry()).append("\n");
        }
        return content.toString();
    }

    /**
     * Stringifies the given language map
     * @param languageMap the language id - value map
     * @return language id - value pairs separeted by new line
     * */
    private String stringifyLanguageMap(Map<NimbleConfigurationProperties.LanguageID, String> languageMap){
        String newLineChar = "\n";
        StringBuilder stringBuilder = new StringBuilder();
        languageMap.forEach((languageID, value) -> {
            List<String> values = Arrays.asList(value.split(newLineChar).clone());
            values.forEach(v -> stringBuilder.append(v).append("(").append(languageID).append(")\n"));
        });
        return stringBuilder.toString();
    }
}
 @Component
public class KafkaSender {

    @Value("${nimble.kafka.topics.companyUpdates}")
    private String companyUpdatesTopic;

    @Value("${nimble.kafka.topics.ratingsUpdates}")
    private String ratingsUpdatesTopic;

    @Autowired
    private KafkaTemplate<String, AuthorizedCompanyUpdate> kafkaTemplate;

    public void broadcastCompanyUpdate(String companyID, String accessToken) {
        accessToken = accessToken.replace("Bearer ", "");
        AuthorizedCompanyUpdate update = new AuthorizedCompanyUpdate(companyID, accessToken);
        kafkaTemplate.send(companyUpdatesTopic, update);
        System.out.println("Message: " + update + " sent to topic: " + companyUpdatesTopic);
    }

    public void broadcastRatingsUpdate(String companyId, String accessToken) {
        accessToken = accessToken.replace("Bearer ", "");
        AuthorizedCompanyUpdate update = new AuthorizedCompanyUpdate(companyId, accessToken);
        kafkaTemplate.send(ratingsUpdatesTopic, update);
        System.out.println("Message: " + update + " sent to topic: " + ratingsUpdatesTopic);
    }
} @Service
public class AdminService {

    private static final Logger logger = LoggerFactory.getLogger(AdminService.class);

    @Autowired
    private PartyRepository partyRepository;

    @Autowired
    private QualifyingPartyRepository qualifyingPartyRepository;

    @Autowired
    private NegotiationSettingsRepository negotiationSettingsRepository;
    @Autowired
    private UserInvitationRepository userInvitationRepository;

    @Autowired
    private PaymentMeansRepository paymentMeansRepository;

    @Autowired
    private ExecutionContext executionContext;

    @Autowired
    private DeliveryTermsRepository deliveryTermsRepository;

    @Autowired
    private DocumentReferenceRepository documentReferenceRepository;
    @Autowired
    private CertificateRepository certificateRepository;
    private BinaryContentService binaryContentService = new BinaryContentService();
    @Autowired
    private KeycloakAdmin keycloakAdmin;

    @Autowired
    private EmailService emailService;

    @Autowired
    private UaaUserRepository uaaUserRepository;

    @Autowired
    private PersonRepository personRepository;


    @Autowired
    private IdentityService identityService;

    @Autowired
    private IndexingClientController indexingController;

    //    @Cacheable("unverifiedCompanies")
    public List<PartyType> queryCompanies(CompanyState companyState) {
        List<PartyType> resultingCompanies = new ArrayList<>();
        Iterable<PartyType> allParties = partyRepository.findAllByDeletedIsFalse(new Sort(Sort.Direction.ASC, "hjid"));
        for (PartyType company : allParties) {

            // collect roles of company members
            Map<PersonType, List<String>> memberRoles = new HashMap<>();
            for (PersonType companyMember : company.getPerson()) {

                // collect roles
                try {
                    Optional<UaaUser> uaaUser = uaaUserRepository.findByUblPerson(companyMember).stream().findFirst();
                    if (uaaUser.isPresent()) {

                        // avoid invalid usernames (only emails are allowed)
                        if (uaaUser.get().getUsername().contains("@") == false)
                            continue;

                        logger.debug("Fetching roles of user {}", uaaUser.get().getUsername());
                        List<String> roles = new ArrayList<>(identityService.fetchRoles(uaaUser.get()));
                        companyMember.setRole(roles);
                        memberRoles.put(companyMember, roles);
                    }
                } catch (NotFoundException ex) {
                    // ignore
                } catch (Exception ex) {
                    logger.error("Error while fetch roles of user", ex);
                }
            }

            // check if unverified check whether at least on member has proper role
            Set<String> mergedRoles = memberRoles.values().stream().flatMap(Collection::stream).collect(Collectors.toSet());
            if (mergedRoles.isEmpty() == false && mergedRoles.contains(LEGAL_REPRESENTATIVE_ROLE) == false && companyState.equals(CompanyState.UNVERIFIED)) {
                resultingCompanies.add(company);
                continue; // avoid multiple entries in list
            } else if (mergedRoles.contains(LEGAL_REPRESENTATIVE_ROLE) && companyState.equals(CompanyState.VERIFIED)) {
                resultingCompanies.add(company);
                continue; // avoid multiple entries in list
            }
        }

        return resultingCompanies;
    }

    public boolean verifyCompany(Long companyId, String bearer) {
        PartyType company = partyRepository.findByHjid(companyId).stream().findFirst().orElseThrow(ControllerUtils.CompanyNotFoundException::new);

        List<PersonType> companyMembers = company.getPerson();

        for (PersonType companyMember : companyMembers) {
            UaaUser uaaUser = uaaUserRepository.findByUblPerson(companyMember).stream().findFirst().orElseThrow(ControllerUtils.PersonNotFoundException::new);
            List<String> roles = new ArrayList<>(keycloakAdmin.getUserRoles(uaaUser.getExternalID()));
            if (roles.contains(INITIAL_REPRESENTATIVE_ROLE)) {
                keycloakAdmin.addRole(uaaUser.getExternalID(), LEGAL_REPRESENTATIVE_ROLE);

                // send email notification
                String email = companyMember.getContact().getElectronicMail();
                //temp. fix for verifying companies created by efactory federated users
                if(email != null && !email.equals("")){
                    emailService.notifyVerifiedCompany(email, companyMember, company,executionContext.getLanguageId());

                }

                //indexing the verified status of the company
                eu.nimble.service.model.solr.party.PartyType party =  indexingController.getNimbleIndexClient().getParty(company.getHjid().toString(),bearer);
                if(party != null) {
                    party.setVerified(true);
                    List<IndexingClient> indexingClients = indexingController.getClients();
                    for (IndexingClient indexingClient : indexingClients) {
                        indexingClient.setParty(party, bearer);
                    }
                }
                return true;
            }
        }

        return false;
    }

    public List<PartyType> sortCompanies(List<PartyType> partyList, String sortBy, String orderBy) {
        Comparator<PartyType> p;

        if (sortBy.equals(GlobalConstants.PARTY_NAME_STRING)) {
            if(GlobalConstants.DESCENDING_STRING.equals(orderBy)){
                p = (p1, p2) ->
                        p2.getPartyName().get(0).getName().getValue().toLowerCase().compareTo(p1.getPartyName().get(0).getName().getValue().toLowerCase());
            }else {
                p = (p1, p2) ->
                        p1.getPartyName().get(0).getName().getValue().toLowerCase().compareTo(p2.getPartyName().get(0).getName().getValue().toLowerCase());
            }
            partyList.sort(p);
        }
        return partyList;
    }

    public boolean revertCompany(Long companyId, String bearer) throws Exception {

        // query company
        PartyType company = partyRepository.findByHjid(companyId).stream().findFirst().orElseThrow(ControllerUtils.CompanyNotFoundException::new);

        if (identityService.hasAnyRole(bearer, OAuthClient.Role.PLATFORM_MANAGER) == true){
            // revert associated company members
            for (PersonType member : company.getPerson()) {
                Long memberHjid = member.getHjid();
                revertPerson(memberHjid,bearer,true);
            }

            //set deleted flag fr the party
            company.setDeleted(false);

            //update the party
            partyRepository.save(company);
            return true;
        }

        else {
            return false;
        }

    }

    public boolean deleteCompany(Long companyId, String bearer, Long userId) throws Exception {

        // query company
        PartyType company = partyRepository.findByHjid(companyId).stream().findFirst().orElseThrow(ControllerUtils.CompanyNotFoundException::new);

        boolean isUserInCompany = false;

        for (PersonType member : company.getPerson()) {
            Long memberHjid = member.getHjid();
            if(memberHjid.equals(userId)){
                isUserInCompany = true;
                break;
            }
        }

        if (identityService.hasAnyRole(bearer, OAuthClient.Role.PLATFORM_MANAGER) == true){
            isUserInCompany = true;
        }

        if(isUserInCompany){
            // delete associated company members
            for (PersonType member : company.getPerson()) {
                Long memberHjid = member.getHjid();
                deletePerson(memberHjid,bearer,true);
            }

            //set deleted flag fr the party
            company.setDeleted(true);

            //update the party
            partyRepository.save(company);

            //update the index by removing the company
            List<IndexingClient> indexingClients = indexingController.getClients();
            for (IndexingClient indexingClient : indexingClients) {
                indexingClient.deleteParty(company.getHjid().toString(), bearer);
            }
            return true;
        }else {
            return false;
        }

//        // delete negotiation settings
//        negotiationSettingsRepository.deleteByCompany(company);
//
//        // delete trading preferences
//        if (company.getPurchaseTerms() != null) {
//            deliveryTermsRepository.delete(company.getPurchaseTerms().getDeliveryTerms());
//            paymentMeansRepository.delete(company.getPurchaseTerms().getPaymentMeans());
//        }
//        if (company.getSalesTerms() != null) {
//            deliveryTermsRepository.delete(company.getSalesTerms().getDeliveryTerms());
//            paymentMeansRepository.delete(company.getSalesTerms().getPaymentMeans());
//        }
//
//        try {
//            // delete for legacy schema
//            deliveryTermsRepository.deleteByPartyID(companyId);
//            paymentMeansRepository.deleteByPartyID(companyId);
//        } catch (InvalidDataAccessResourceUsageException ex) {
//            // ignored
//        }
//
//        return partyRepository.deleteByHjid(companyId);

    }

    // this method deletes the party represented by the given id
    public void deleteCompanyPermanently(Long companyId) {
        // query company
        PartyType company = partyRepository.findByHjid(companyId).stream().findFirst().orElseThrow(ControllerUtils.CompanyNotFoundException::new);
        // retrieve qualifying party
        QualifyingPartyType qualifyingParty = qualifyingPartyRepository.findByParty(company).stream().findFirst().get();
        // retrieve negotiation settings
        NegotiationSettings negotiationSettings = negotiationSettingsRepository.findOneByCompany(company);
        if(negotiationSettings != null){
            // delete negotiation settings
            negotiationSettingsRepository.delete(negotiationSettings);
        }

        // delete qualifying party
        qualifyingPartyRepository.delete(qualifyingParty);

        // delete trading preferences
        if (company.getPurchaseTerms() != null) {
            deliveryTermsRepository.delete(company.getPurchaseTerms().getDeliveryTerms());
            paymentMeansRepository.delete(company.getPurchaseTerms().getPaymentMeans());
        }
        if (company.getSalesTerms() != null) {
            deliveryTermsRepository.delete(company.getSalesTerms().getDeliveryTerms());
            paymentMeansRepository.delete(company.getSalesTerms().getPaymentMeans());
        }

        // delete document references
        if(company.getDocumentReference() != null){
            for (DocumentReferenceType documentReference : company.getDocumentReference()) {
                if(documentReference.getAttachment() != null && documentReference.getAttachment().getEmbeddedDocumentBinaryObject() != null && documentReference.getAttachment().getEmbeddedDocumentBinaryObject().getUri() != null){
                    String uri = documentReference.getAttachment().getEmbeddedDocumentBinaryObject().getUri();
                    binaryContentService.deleteContentIdentity(uri);
                }

                documentReferenceRepository.delete(documentReference);
            }
        }

        // delete company certificates
        if(company.getCertificate() != null){
            for (CertificateType certificate : company.getCertificate()) {

                if(certificate.getDocumentReference() != null && certificate.getDocumentReference().get(0).getAttachment() != null && certificate.getDocumentReference().get(0).getAttachment().getEmbeddedDocumentBinaryObject() != null && certificate.getDocumentReference().get(0).getAttachment().getEmbeddedDocumentBinaryObject().getUri() != null){
                    String uri = certificate.getDocumentReference().get(0).getAttachment().getEmbeddedDocumentBinaryObject().getUri();
                    binaryContentService.deleteContentIdentity(uri);
                }

                certificateRepository.delete(certificate);
            }

        }

        // delete party
        partyRepository.delete(company);
    }

    public boolean deletePerson(Long personHjid, String bearer , boolean isCompanyDelete) throws Exception {

        OpenIdConnectUserDetails oidUser = identityService.getUserDetails(bearer);
        String keyCloackuid = oidUser.getUserId();

        // query person
        PersonType person = personRepository.findByHjid(personHjid).stream().findFirst().orElseThrow(ControllerUtils.PersonNotFoundException::new);
        List<UaaUser> potentialUser = uaaUserRepository.findByUblPerson(person);
        UaaUser uaaUser = potentialUser.stream().findFirst().orElseThrow(() -> new Exception("Invalid user mapping"));
        String keyCloakId = uaaUser.getExternalID();

        if(keyCloakId.equals(keyCloackuid) || identityService.hasAnyRole(bearer, OAuthClient.Role.PLATFORM_MANAGER) == true
                || isCompanyDelete){
            //set delete flag for the person
            person.setDeleted(true);
            keycloakAdmin.addRole(keyCloakId, KeycloakAdmin.NIMBLE_DELETED_USER);
            //save deleted person
            personRepository.save(person);
            return true;
        }else{
            return false;
        }
    }

    public boolean revertPerson(Long personHjid, String bearer , boolean isCompanyReverted) throws Exception {

        OpenIdConnectUserDetails oidUser = identityService.getUserDetails(bearer);
        String keyCloackuid = oidUser.getUserId();

        // query person
        PersonType person = personRepository.findByHjid(personHjid).stream().findFirst().orElseThrow(ControllerUtils.PersonNotFoundException::new);
        List<UaaUser> potentialUser = uaaUserRepository.findByUblPerson(person);
        UaaUser uaaUser = potentialUser.stream().findFirst().orElseThrow(() -> new Exception("Invalid user mapping"));
        String keyCloakId = uaaUser.getExternalID();

        if(keyCloakId.equals(keyCloackuid) || identityService.hasAnyRole(bearer, OAuthClient.Role.PLATFORM_MANAGER) == true
                || isCompanyReverted){
            //set delete flag for the person
            person.setDeleted(false);
            keycloakAdmin.removeRole(keyCloakId, KeycloakAdmin.NIMBLE_DELETED_USER);
            //save person
            personRepository.save(person);
            return true;
        }else{
            return false;
        }
    }

    public enum CompanyState {
        VERIFIED, UNVERIFIED
    }
} @Service
public class IdentityService {

    private static final Logger logger = LoggerFactory.getLogger(IdentityService.class);

    @Autowired
    private UaaUserRepository uaaUserRepository;

    @Autowired
    private PartyRepository partyRepository;

    @Autowired
    private KeycloakAdmin keycloakAdmin;

    public UaaUser getUserfromBearer(String bearer) throws IOException {
        OpenIdConnectUserDetails userDetails = getUserDetails(bearer);
        return uaaUserRepository.findByExternalID(userDetails.getUserId());
    }

    public OpenIdConnectUserDetails getUserDetails(String bearer) throws IOException {
        return OpenIdConnectUserDetails.fromBearer(bearer);
    }

    /**
     * Checks if the bearer contains at least one of the given roles.
     *
     * @param bearer Token containing roles
     * @param roles  Roles to check
     * @return True if at least one matching role was found.
     * @throws IOException if roles could not be extracted from token
     */
    public boolean hasAnyRole(String bearer, OAuthClient.Role... roles) throws IOException {
        OpenIdConnectUserDetails details = getUserDetails(bearer);
        return Arrays.stream(roles).anyMatch(r -> details.hasRole(r.toString()));
    }

    public Optional<PartyType> getCompanyOfUser(UaaUser uaaUser) {
        if (uaaUser == null)
            return Optional.empty();
        return partyRepository.findByPerson(uaaUser.getUBLPerson()).stream().findFirst();
    }

    @SuppressWarnings("SimplifiableIfStatement")
    public boolean inSameCompany(UaaUser userOne, UaaUser userTwo) {

        if (userOne == null || userTwo == null)
            return false;

        Optional<PartyType> requestingCompany = getCompanyOfUser(userOne);
        Optional<PartyType> targetCompany = getCompanyOfUser(userTwo);
        if ((requestingCompany.isPresent() && targetCompany.isPresent()) == false) // check if companies exist
            return false;

        return requestingCompany.get().getHjid().equals(targetCompany.get().getHjid());
    }

    public Set<String> fetchRoles(PersonType personType) {
        try {
            UaaUser uaaUser = uaaUserRepository.findByUblPerson(personType).stream().findFirst().orElseThrow(NotFoundException::new);
            return fetchRoles(uaaUser);
        } catch (Exception exception) {
            logger.error("Error while fetching roles for person", exception);
        }
        return Collections.emptySet();
    }

    public Set<String> fetchRoles(UaaUser user) {

        Set<String> roles = new HashSet<>();
        try {
            // collect roles
            logger.debug("Fetching roles of user {}", user.getUsername());
            roles = keycloakAdmin.getUserRoles(user.getExternalID(), KeycloakAdmin.NON_NIMBLE_ROLES);
        } catch (Exception ex) {
            logger.error("Error while fetch roles of user", ex);
        }

        return roles;
    }

    public void enrichWithRoles(PersonType person) {
        Set<String> roles = fetchRoles(person);
        person.getRole().clear();
        person.getRole().addAll(roles);
    }

    public void enrichWithRoles(PartyType party) {
        party.getPerson().forEach(this::enrichWithRoles);
    }

    public static Double computeDetailsCompleteness(CompanyDetails companyDetails) {

        List<Double> completenessWeights = new ArrayList<>();
        completenessWeights.add(companyDetails.getLegalName().isEmpty() == false ? 1.0 : 0.0);
        completenessWeights.add(StringUtils.isNotEmpty(companyDetails.getVatNumber()) ? 1.0 : 0.0);
        completenessWeights.add(StringUtils.isNotEmpty(companyDetails.getBusinessType()) ? 1.0 : 0.0);
        completenessWeights.add(companyDetails.getIndustrySectors() != null && companyDetails.getIndustrySectors().size() > 0 ? 1.0 : 0.0);

        Address address = companyDetails.getAddress();
        if (address != null) {
            completenessWeights.add(StringUtils.isNotEmpty(address.getStreetName()) ? 1.0 : 0.0);
            completenessWeights.add(StringUtils.isNotEmpty(address.getCityName()) ? 1.0 : 0.0);
            completenessWeights.add(StringUtils.isNotEmpty(address.getPostalCode()) ? 1.0 : 0.0);
            completenessWeights.add(StringUtils.isNotEmpty(address.getCountry()) ? 1.0 : 0.0);
        }
        return completenessWeights.stream().mapToDouble(d -> d).average().orElse(0.0);
    }

    public static Double computeDescriptionCompleteness(CompanyDescription companyDescription) {
        List<Double> completenessWeights = new ArrayList<>();
        completenessWeights.add(!companyDescription.getCompanyStatement().isEmpty() ? 1.0 : 0.0);
        completenessWeights.add(StringUtils.isNotEmpty(companyDescription.getWebsite()) ? 1.0 : 0.0);
        completenessWeights.add(companyDescription.getLogoImageId() != null ? 1.0 : 0.0);
        completenessWeights.add(companyDescription.getSocialMediaList() != null && companyDescription.getSocialMediaList().size() > 0 ? 1.0 : 0.0);
        return completenessWeights.stream().mapToDouble(d -> d).average().orElse(0.0);
    }

    public static Double computeDeliveryAddressCompleteness(PartyType party) {
        List<Double> completenessWeights = new ArrayList<>();
        TradingPreferences tradingPreferences = party.getPurchaseTerms();
        if (tradingPreferences.getDeliveryTerms().size() == 0 || null == tradingPreferences.getDeliveryTerms().get(0).getDeliveryLocation()) {
            return 0.0;
        } else {
            LocationType locationType = tradingPreferences.getDeliveryTerms().get(0).getDeliveryLocation();
            completenessWeights.add(locationType.getAddress().getStreetName() != null ? 1.0 : 0.0);
            completenessWeights.add(locationType.getAddress().getBuildingNumber() != null ? 1.0 : 0.0);
            completenessWeights.add(locationType.getAddress().getCityName() != null ? 1.0 : 0.0);
            completenessWeights.add(locationType.getAddress().getRegion() != null ? 1.0 : 0.0);
            completenessWeights.add(locationType.getAddress().getPostalZone() != null ? 1.0 : 0.0);
            completenessWeights.add(locationType.getAddress().getCountry() != null ? 1.0 : 0.0);
            return completenessWeights.stream().mapToDouble(d -> d).average().orElse(0.0);
        }
    }

    public static Double computeCertificateCompleteness(PartyType party) {
        List<Double> completenessWeights = new ArrayList<>();
        completenessWeights.add(party.getCertificate() != null && party.getCertificate().size() > 0 ? 1.0 : 0.0);
        return completenessWeights.stream().mapToDouble(d -> d).average().orElse(0.0);
    }

    public static Double computeTradeCompleteness(NegotiationSettings negotiationSettings) {
        List<Double> completenessWeights = new ArrayList<>();
        try {
            completenessWeights.add(negotiationSettings.getPaymentMeans() != null && negotiationSettings.getPaymentMeans().size() > 0 ? 1.0 : 0.0);
            completenessWeights.add(negotiationSettings.getPaymentTerms() != null && negotiationSettings.getPaymentTerms().size() > 0 ? 1.0 : 0.0);
            completenessWeights.add(negotiationSettings.getIncoterms() != null && negotiationSettings.getIncoterms().size() > 0 ? 1.0 : 0.0);
        }catch (Exception e){
            logger.error("Exception occurred while computing the tradCompletenessSocre", e);
        }
        return completenessWeights.stream().mapToDouble(d -> d).average().orElse(0.0);
    }

    public static Double computeAdditionalDataCompleteness(PartyType party, CompanyTradeDetails tradeDetails, CompanyDescription companyDescription) {
        List<Double> completenessWeights = new ArrayList<>();
        completenessWeights.add(companyDescription.getEvents() != null && companyDescription.getEvents().size() > 0 ? 1.0 : 0.0);
        completenessWeights.add(companyDescription.getCompanyPhotoList() != null && companyDescription.getCompanyPhotoList().size() > 0 ? 1.0 : 0.0);
        completenessWeights.add(companyDescription.getExternalResources() != null && companyDescription.getExternalResources().size() > 0 ? 1.0 : 0.0);
        return completenessWeights.stream().mapToDouble(d -> d).average().orElse(0.0);
    }
} @Service
public class RocketChatService {

    private static final Logger logger = LoggerFactory.getLogger(IdentityController.class);

    @Value("${nimble.rocketChat.url}")
    private String rocketChatURL;

    @Value("${nimble.rocketChat.user}")
    private String rocketChatUser;

    @Value("${nimble.rocketChat.password}")
    private String rocketChatPassword;

    @Value("${nimble.rocketChat.chatServiceURL}")
    private String chatServiceURL;

    @Value("${nimble.rocketChat.isEnabled}")
    private boolean isChatEnabled;

    public CreateChannelRequest createChannel(CreateChannelRequest createChannelRequest, List<String> members) {

        String uri = rocketChatURL + "/api/v1/channels.create";

        JSONObject request = new JSONObject();
        request.put("name", createChannelRequest.getChannelName());
        request.put("members", members);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("X-User-Id", createChannelRequest.getUserId());
        headers.set("X-Auth-Token", createChannelRequest.getUserToken());
        HttpEntity<String> entity = new HttpEntity<String>(request.toString(), headers);

        RestTemplate rs = new RestTemplate();

        try {
            ResponseEntity<String> response = rs.exchange(uri, HttpMethod.POST, entity, String.class);
            ObjectMapper mapper = new ObjectMapper();
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            CreateChannelResponse createChannelResponse = new CreateChannelResponse();

            createChannelResponse = mapper.readValue(response.getBody(), CreateChannelResponse.class);
            // handle channel name conflict
            if (!createChannelResponse.isSuccess()) {

            } else {
                persistChannelName(createChannelRequest);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return createChannelRequest;
    }

    /**
     * @param createChannelRequest
     * @return
     */
    public String checkIfChannelExist(CreateChannelRequest createChannelRequest) {

        RestTemplate rs = new RestTemplate();
        String uri = chatServiceURL + "/channels";

        JSONObject request = new JSONObject();
        request.put("catalogueID", createChannelRequest.getProductName());
        request.put("initiatingPartyID", createChannelRequest.getInitiatingPartyID());
        request.put("respondingPartyID", createChannelRequest.getRespondingPartyID());

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<String>(request.toString(), headers);

        try {
            ResponseEntity<String> registerResponse = rs.exchange(uri, HttpMethod.POST, entity, String.class);
            if (registerResponse.getStatusCode().value() == 200) {
                ObjectMapper mapper = new ObjectMapper();
                mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                CreateChannelRequest rq = mapper.readValue(registerResponse.getBody(), CreateChannelRequest.class);
                logger.info("Channel name : {} found in chat service", rq.getChannelName());
                return rq.getChannelName();
            } else {
                logger.info("Existing channel not found for: {}", createChannelRequest.toString());
                return null;
            }
        } catch (Exception e) {
            logger.error("Error when obtaining channel data from chat service: {}", createChannelRequest.toString());
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param createChannelRequest
     * @return
     */
    public boolean persistChannelName(CreateChannelRequest createChannelRequest) {

        RestTemplate rs = new RestTemplate();
        String uri = chatServiceURL + "/channel";

        JSONObject request = new JSONObject();
        request.put("catalogueID", createChannelRequest.getProductName());
        request.put("initiatingPartyID", createChannelRequest.getInitiatingPartyID());
        request.put("respondingPartyID", createChannelRequest.getRespondingPartyID());
        request.put("channelName", createChannelRequest.getChannelName());

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<String>(request.toString(), headers);

        try {
            ResponseEntity<String> registerResponse = rs.exchange(uri, HttpMethod.POST, entity, String.class);
            if (registerResponse.getStatusCode().value() == 201) {
                logger.info("Channel name : {} persisted in chat service", createChannelRequest.getChannelName());
                return true;
            } else {
                logger.info("Error when persisting channel : {} , name in chat service", createChannelRequest.getChannelName());
                return false;
            }
        } catch (Exception e) {
            logger.info("Error when persisting channel : {} , name in chat service", createChannelRequest.getChannelName());
            e.printStackTrace();
        }
        return false;
    }


    /**
     * This function will return the missing users in Rocket.Chat in a Map
     *
     * @param chatUsers
     * @return
     */
    public Map<String, String> getMissingEmails(ChatUsers chatUsers, List<String> initiatingParty, List<String> respondingList) {
        Map<String, String> emailUsernameMap = new HashMap<>();


        for (String m : initiatingParty) {
            for (ChatUser c : chatUsers.getUsers()) {
                if (null != c.getEmails()) {
                    for (UserEmail e : c.getEmails()) {
                        if (e.getAddress().equals(m)) {
                            emailUsernameMap.put(e.getAddress(), c.getUsername());
                        }
                    }
                }
            }
        }

        for (String m : respondingList) {
            for (ChatUser c : chatUsers.getUsers()) {
                if (null != c.getEmails()) {
                    for (UserEmail e : c.getEmails()) {
                        if (e.getAddress().equals(m)) {
                            emailUsernameMap.put(e.getAddress(), c.getUsername());
                        }
                    }

                }
            }
        }

        for (String m : initiatingParty) {
            emailUsernameMap.putIfAbsent(m, null);
        }
        for (String m : respondingList) {
            emailUsernameMap.putIfAbsent(m, null);
        }

        return emailUsernameMap;
    }

    public ChatUsers listUsers() {
        Credentials credentials = new Credentials();
        credentials.setUsername(rocketChatUser);
        credentials.setPassword(rocketChatPassword);
        RocketChatLoginResponse rocketChatLoginResponse = loginOrCreateUser(new FrontEndUser(), credentials, false, false);
        return listUsers(rocketChatLoginResponse.getData().getAuthToken(), rocketChatLoginResponse.getData().getUserId());
    }

    /**
     * This function uses the admin users credentials to obtain user list as normal users credentials will only fetch user name
     *
     * @param authToken
     * @param userID
     * @return
     */
    public ChatUsers listUsers(String authToken, String userID) {

        ChatUsers chatUsers = new ChatUsers();
        String uri = rocketChatURL + "/api/v1/users.list";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("X-User-Id", userID);
        headers.set("X-Auth-Token", authToken);
        HttpEntity<String> entity = new HttpEntity<String>(headers);

        RestTemplate rs = new RestTemplate();
        try {

            ResponseEntity<String> response = rs.exchange(uri, HttpMethod.GET, entity, String.class);
            ObjectMapper mapper = new ObjectMapper();
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

            try {
                chatUsers = mapper.readValue(response.getBody(), ChatUsers.class);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return chatUsers;
    }

    /**
     * The following method registers a user in Rocket.Chat, User name will be created firstname.lastname.email_domain
     *
     * @param frontEndUser
     * @param credentials
     * @param userNameAlreadyExists
     * @return
     */
    public RockerChatRegisterResponse registerUser(FrontEndUser frontEndUser, Credentials credentials, boolean userNameAlreadyExists, int retryCounter) {

        RockerChatRegisterResponse rockerChatRegisterResponse = new RockerChatRegisterResponse();

        try {
            RestTemplate rs = new RestTemplate();
            String uri = rocketChatURL + "/api/v1/users.register";

            String temp = credentials.getUsername().substring(credentials.getUsername().indexOf("@") + 1); // e.g. @google.com
            String companyEmailName = temp.substring(0, temp.indexOf("."));

            String rocketChatUserName = frontEndUser.getFirstname() + "." + frontEndUser.getLastname() + "." + companyEmailName;
            if (userNameAlreadyExists) {
                rocketChatUserName = rocketChatUserName + ((int) (Math.random() * 100));
            }

            JSONObject request = new JSONObject();
            request.put(GlobalConstants.EMAIL_STRING, credentials.getUsername());
            request.put("pass", new Md5PasswordEncoder().encodePassword(credentials.getUsername(), null).substring(0, 8));
            request.put(GlobalConstants.USER_NAME_STRING, rocketChatUserName);
            request.put("name", frontEndUser.getFirstname() + " " + frontEndUser.getLastname());

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            HttpEntity<String> entity = new HttpEntity<String>(request.toString(), headers);


            ResponseEntity<String> registerResponse = rs.exchange(uri, HttpMethod.POST, entity, String.class);
            ObjectMapper mapper = new ObjectMapper();
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            rockerChatRegisterResponse = new RockerChatRegisterResponse();
            rockerChatRegisterResponse = mapper.readValue(registerResponse.getBody(), RockerChatRegisterResponse.class);

            if (rockerChatRegisterResponse.isSuccess() && null == rockerChatRegisterResponse.getError()) {
                logger.info("A new user have been created in Rocket.Chat with username {}", rockerChatRegisterResponse.getUser().getUsername());
            }

            // If the user name already exists then a new user name should be created
            if (rockerChatRegisterResponse.isSuccess() && null != rockerChatRegisterResponse.getError()) {
                if (rockerChatRegisterResponse.getError().equals("Username is already in use") && retryCounter <= 1) {
                    logger.info("User name conflict when creating a new user with username {}", rocketChatUserName);
                    rockerChatRegisterResponse = registerUser(frontEndUser, credentials, true, retryCounter + 1);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return rockerChatRegisterResponse;
    }

    /**
     * The following method deletes a user from Rocket.Chat
     *
     * @param userEmailAddress
     * @return
     */
    public void deleteUser(String userEmailAddress) {
        // find the user for the given email address and delete it from Rocket.Chat
        ChatUsers chatUsers = listUsers();
        for (ChatUser user : chatUsers.getUsers()) {
            if(user.getEmails() != null){
                for (UserEmail userEmail : user.getEmails()) {
                    if(userEmail.getAddress().contentEquals(userEmailAddress)){
                        // delete the user from Rocket.Chat
                        Credentials credentials = new Credentials();
                        credentials.setUsername(rocketChatUser);
                        credentials.setPassword(rocketChatPassword);
                        RocketChatLoginResponse rocketChatLoginResponse = loginOrCreateUser(new FrontEndUser(), credentials, false, false);

                        RestTemplate rs = new RestTemplate();
                        String uri = rocketChatURL + "/api/v1/users.delete";

                        HttpHeaders headers = new HttpHeaders();
                        headers.setContentType(MediaType.APPLICATION_JSON);
                        headers.set("X-User-Id", rocketChatLoginResponse.getData().getUserId());
                        headers.set("X-Auth-Token", rocketChatLoginResponse.getData().getAuthToken());

                        JSONObject request = new JSONObject();
                        request.put(GlobalConstants.USER_NAME_STRING, user.getUsername());

                        HttpEntity<String> entity = new HttpEntity<String>(request.toString(), headers);

                        ResponseEntity<String> registerResponse = rs.exchange(uri, HttpMethod.POST, entity, String.class);
                        ObjectMapper mapper = new ObjectMapper();
                        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

                        try{
                            RocketChatDeleteResponse rocketChatDeleteResponse = mapper.readValue(registerResponse.getBody(), RocketChatDeleteResponse.class);

                            if (rocketChatDeleteResponse.isSuccess() && null == rocketChatDeleteResponse.getError()) {
                                logger.info("The user with username {} is deleted from Rocket.Chat", user.getUsername());
                            }

                            if (!rocketChatDeleteResponse.isSuccess()) {
                                String error = rocketChatDeleteResponse.getError() != null ? rocketChatDeleteResponse.getError(): "";
                                logger.error("Failed to delete the user with username {} from Rocket.Chat {}", user.getUsername(),error);
                            }

                        }catch (Exception e){
                            e.printStackTrace();
                        }

                        return;
                    }
                }
            }
        }
    }

    /**
     * This function tries to login a user to Rocket.Chat, if the user is not present in Rocket.Chat then it will create
     * a new user in Rocket.Chat. This behaviour has been added to support existing users in the platform
     *
     * @param frontEndUser
     * @param credentials
     * @return
     */
    public RocketChatLoginResponse loginOrCreateUser(FrontEndUser frontEndUser, Credentials credentials, boolean createIfMissing, boolean generatePass) {

        RocketChatLoginResponse rocketChatLoginResponse = new RocketChatLoginResponse();

        try {
            RestTemplate rs = new RestTemplate();
            String uri = rocketChatURL + "/api/v1/login";

            JSONObject request = new JSONObject();
            request.put(GlobalConstants.EMAIL_STRING, credentials.getUsername());
            if (generatePass) {
                request.put("password", new Md5PasswordEncoder().encodePassword(credentials.getUsername(), null).substring(0, 8));
            } else {
                request.put("password", credentials.getPassword());
            }

            ObjectMapper mapper = new ObjectMapper();
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            HttpEntity<String> entity = new HttpEntity<String>(request.toString(), headers);

            ResponseEntity<String> loginResponse = rs.exchange(uri, HttpMethod.POST, entity, String.class);
            rocketChatLoginResponse = mapper.readValue(loginResponse.getBody(), RocketChatLoginResponse.class);

        } catch (HttpStatusCodeException exception) {
            if (exception.getStatusCode().value() == 401 && createIfMissing) {
                RockerChatRegisterResponse rockerChatRegisterResponse = registerUser(frontEndUser, credentials, false, 0);
                if (rockerChatRegisterResponse.isSuccess()) {
                    rocketChatLoginResponse = loginOrCreateUser(frontEndUser, credentials, false, true);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return rocketChatLoginResponse;
    }

    public boolean isChatEnabled() {
        return isChatEnabled;
    }
}
 @SuppressWarnings("Convert2MethodRef")
@Service
public class KeycloakAdmin {

    private static final Logger logger = LoggerFactory.getLogger(KeycloakAdmin.class);

    public static final String NIMBLE_USER_ROLE = "nimble_user";
    public static final String INITIAL_REPRESENTATIVE_ROLE = "initial_representative";
    public static final String LEGAL_REPRESENTATIVE_ROLE = "legal_representative";
    public static final String PLATFORM_MANAGER_ROLE = "platform_manager";
    public static final String NIMBLE_DELETED_USER = "nimble_deleted_user";

    public static final String PLATFORM_MANAGER_GROUP = "Platform Manager";

    public static final List<String> NON_ASSIGNABLE_ROLES = Arrays.asList("platform_manager", "uma_authorization",
            "offline_access", "admin", "create-realm",
            "create-realm", "nimble_user", "initial_representative");

    public static final List<String> NON_NIMBLE_ROLES = Arrays.asList("uma_authorization", "offline_access", "admin", "create-realm", "create-realm");

    @Value("${nimble.oauth.identityProvider.eFactory}")
    private String eFactoryIdentityProvider;

    @Autowired
    private KeycloakConfig keycloakConfig;

    @Autowired
    private OAuthClient oAuthClient;

    @Autowired
    private OAuthClientConfig oAuthClientConfig;

    private Keycloak keycloak;

    private static long oneHourInMilliSeconds = 3600000;

    @PostConstruct
    @SuppressWarnings("unused")
    public void init() {
        ResteasyClient client = new ResteasyClientBuilder().connectionPoolSize(10).build();
        this.keycloak = KeycloakBuilder.builder()
                .serverUrl(keycloakConfig.getServerUrl())
                .realm(keycloakConfig.getRealm())
                .grantType(OAuth2Constants.PASSWORD)
                .username(keycloakConfig.getAdmin().getUsername())
                .password(keycloakConfig.getAdmin().getPassword())
                .clientId(keycloakConfig.getAdmin().getCliendId())
                .clientSecret(keycloakConfig.getAdmin().getCliendSecret())
                .resteasyClient(client)
                .build();
    }

    public String initiatePasswordRecoveryProcess(String email) {
        RealmResource realmResource = this.keycloak.realm(keycloakConfig.getRealm());
        UsersResource userResource = realmResource.users();

        List<UserRepresentation> userList = userResource.search(email);
        if (userList.size() != 0) {
            UserRepresentation user = userList.get(0);
            return JWT.create()
                    .withJWTId(user.getId())
                    .withClaim(GlobalConstants.JWT_TYPE_ATTRIBUTE_STRING, "reset-credentials")
                    .withSubject(user.getId())
                    .withExpiresAt(new Date(System.currentTimeMillis() + oneHourInMilliSeconds))
                    .sign(HMAC512(keycloakConfig.getAdmin().getCliendSecret().getBytes()));
        }else {
            throw new ResourceNotFoundException("Resource not found for the user name");
        }
    }

    public void resetPasswordViaRecoveryProcess(String token, String newPassword) {

        try {
            JWTVerifier verifier = JWT.require(HMAC512(keycloakConfig.getAdmin().getCliendSecret().getBytes())).build();
            verifier.verify(token);
        } catch (SignatureVerificationException e) {
            logger.warn("Invalid token received for password reset");
            throw new BadRequestException("Invalid Token");
        }

        long exp = JWT.decode(token).getClaim(GlobalConstants.JWT_EXPIRY_ATTRIBUTE_STRING).asLong();
        if (System.currentTimeMillis() > exp) {
            String sub = JWT.decode(token).getClaim(GlobalConstants.JWT_SUBJECT_ATTRIBUTE_STRING).asString();
            RealmResource realmResource = this.keycloak.realm(keycloakConfig.getRealm());
            UserResource use = realmResource.users().get(sub);
            CredentialRepresentation passwordCredential = createPasswordCredentials(newPassword);
            passwordCredential.setTemporary(false);
            this.keycloak.realm(keycloakConfig.getRealm()).users().get(sub).resetPassword(passwordCredential);
        }else {
            throw new NotAuthorizedException("URL expired");
        }
    }

    /**
     * Verify the jwt token provided by the Keycloak
     * TODO: Implement verification by using the certs
     * https://stackoverflow.com/questions/39890232/how-to-decode-keys-from-keycloak-openid-connect-cert-api
     * @param jwtToken
     * @return
     */
    public boolean verify(String jwtToken) throws ServerException {

        String uri = keycloakConfig.getServerUrl() + "/realms/master";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<String>(headers);
        RestTemplate rs = new RestTemplate();

        try {
            ResponseEntity<String> response = rs.exchange(uri, HttpMethod.GET, entity, String.class);
            ObjectMapper mapper = new ObjectMapper();
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

            RealmConfigs realmConfigs = mapper.readValue(response.getBody(), RealmConfigs.class);
            JwtHelper.decodeAndVerify(jwtToken, new RsaVerifier(getRSAPublicKey(realmConfigs.getPublic_key())));
        } catch (IOException e) {
            logger.error("Error in extracting the data from Keycloak realm response{}", e);
            throw new ServerException("Keycloak server error", e);
        } catch (Exception e) {
            logger.error("Error in verifying token{}", e);
            return false;
        }
        return true;
    }


    /**
     * Generates the RSA Public key from Keycloak public key
     * @param publicKey
     * @return
     * @throws Exception
     */
    private RSAPublicKey getRSAPublicKey(String publicKey) throws Exception {
        if( StringUtils.isBlank(publicKey)) return null;
        try {
            KeyFactory keyFactory = java.security.KeyFactory.getInstance("RSA");
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(java.util.Base64.getDecoder().decode(publicKey));
            return (RSAPublicKey) keyFactory.generatePublic(keySpec);
        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
            logger.error("Error forming RSA key {}", e);
            throw new Exception(e);
        }
    }

    /**
     * throws javax.ws.rs.WebApplicationException with corrensponding response for error
     **/
    public String registerUser(String firstName, String lastName, String password, String email) {

        RealmResource realmResource = this.keycloak.realm(keycloakConfig.getRealm());
        UsersResource userResource = realmResource.users();

        // create proper credentials
        CredentialRepresentation passwordCredentials = createPasswordCredentials(password);

        // create user
        UserRepresentation user = new UserRepresentation();
        user.setUsername(email);
        user.setCredentials(Collections.singletonList(passwordCredentials));
        user.setFirstName(firstName);
        user.setLastName(lastName);
        user.setEmail(email);
        user.setRequiredActions(Collections.emptyList());
        user.setEnabled(true);
        user.setEmailVerified(false);

        // extract identifier of user
        Response response = userResource.create(user);
        String userId = extractCreatedId(response);
        UserResource createdUser = fetchUserResource(userId);

        // ToDo: throw exception if user already exists

        // set password
        createdUser.resetPassword(passwordCredentials);

//        // send verification mail
//        createdUser.executeActionsEmail(oAuthClientConfig.getCliendId(), "http://localhost:102", Collections.singletonList("VERIFY_EMAIL"));
//        createdUser.sendVerifyEmail(oAuthClientConfig.getCliendId());

        addRole(userId, NIMBLE_USER_ROLE);

        return createdUser.toRepresentation().getId();
    }

    public void deleteUser(String externalId) {
        RealmResource realmResource = this.keycloak.realm(keycloakConfig.getRealm());
        UsersResource userResource = realmResource.users();

        // delete user
        userResource.delete(externalId);
    }

    public void deleteUserByUsername(String username) {
        RealmResource realmResource = this.keycloak.realm(keycloakConfig.getRealm());
        UsersResource userResource = realmResource.users();

        List<UserRepresentation> userRepresentations = userResource.search(username);
        userRepresentations.forEach(userRepresentation -> userResource.delete(userRepresentation.getId()));
    }

    public Map<String, String> getAssignableRoles() {
        RealmResource realmResource = this.keycloak.realm(keycloakConfig.getRealm());

        return realmResource.roles().list().stream()
                .filter(r -> NON_ASSIGNABLE_ROLES.contains(r.getName()) == false)
                .collect(Collectors.toMap(r -> r.getId(), r -> r.getName()));
    }

    public Set<String> getUserRoles(String userId) {
        return getUserRoles(userId, new ArrayList<>());
    }

    public Set<String> getUserRoles(String userId, List<String> excludeRoles) {

        List<String> finalExcludeRoles = (excludeRoles == null) ? new ArrayList<>() : excludeRoles;
        UserResource userResource = fetchUserResource(userId);
        return userResource.roles().realmLevel().listAll().stream()
                .map(r -> r.getName())
                .filter(role -> finalExcludeRoles.contains(role) == false)
                .collect(Collectors.toSet());
    }

    public UserResource getUserResource(String userId) {
        return fetchUserResource(userId);
    }

    public String getEFactoryUserId(UserResource userResource){
        for (FederatedIdentityRepresentation federatedIdentityRepresentation : userResource.getFederatedIdentity()) {
            if(federatedIdentityRepresentation.getIdentityProvider().contentEquals(eFactoryIdentityProvider)){
                    return federatedIdentityRepresentation.getUserId();
            }
        }
        return null;
    }
    public void addRole(String userId, String role) {
        UserResource userResource = fetchUserResource(userId);

        RealmResource realmResource = this.keycloak.realm(keycloakConfig.getRealm());
        RoleRepresentation roleRepresentation = realmResource.roles().get(role).toRepresentation();
        userResource.roles().realmLevel().add(Collections.singletonList(roleRepresentation));
    }

    public void removeRole(String userId, String role) {
        UserResource userResource = fetchUserResource(userId);

        RealmResource realmResource = this.keycloak.realm(keycloakConfig.getRealm());
        RoleRepresentation roleRepresentation = realmResource.roles().get(role).toRepresentation();
        userResource.roles().realmLevel().remove(Collections.singletonList(roleRepresentation));
    }

    public List<UserRepresentation> getPlatformManagers() {

        RealmResource realmResource = this.keycloak.realm(keycloakConfig.getRealm());
        List<GroupRepresentation> groups = realmResource.groups().groups();

        Optional<GroupRepresentation> platformManagerGroup = groups.stream().filter(g -> "Platform Manager".equals(g.getName())).findFirst();
        if (platformManagerGroup.isPresent() == false) {
            logger.warn("No platform managers found!");
            return new ArrayList<>();  // empty list as fallback
        }
        return realmResource.groups().group(platformManagerGroup.get().getId()).members();
    }

    private UserResource fetchUserResource(String userId) {
        RealmResource realmResource = this.keycloak.realm(keycloakConfig.getRealm());
        return realmResource.users().get(userId);
    }

    public boolean resetPassword(UaaUser user, String oldPassword, String newPassword) {

        try {
            // verify existing password
            OAuth2AccessToken accessToken = oAuthClient.getToken(user.getUsername(), oldPassword);
            if (accessToken == null)
                return false;

            // set new password
            CredentialRepresentation passwordCredential = createPasswordCredentials(newPassword);
            this.keycloak.realm(keycloakConfig.getRealm()).users().get(user.getExternalID()).resetPassword(passwordCredential);
        } catch (OAuth2AccessDeniedException ex) {
            logger.info("Authentication error while setting new password");
            return false;
        }

        return true;
    }

    private String extractCreatedId(Response response) {
        URI location = response.getLocation();
        if (!response.getStatusInfo().equals(Response.Status.CREATED)) {
            Response.StatusType statusInfo = response.getStatusInfo();
            throw new WebApplicationException("Create method returned status " +
                    statusInfo.getReasonPhrase() + " (Code: " + statusInfo.getStatusCode() + "); expected status: Created (201)",
                    statusInfo.getStatusCode());
        }
        if (location == null) {
            return null;
        }
        String path = location.getPath();
        return path.substring(path.lastIndexOf('/') + 1);
    }

    public int applyRoles(String userID, Set<String> rolesToApply) {
        // setting proper set of roles
        Set<String> currentRoles = getUserRoles(userID, NON_ASSIGNABLE_ROLES);
        Set<String> rolesToRemove = Sets.difference(currentRoles, rolesToApply);
        Set<String> rolesToAdd = Sets.difference(rolesToApply, currentRoles);
        logger.info("Applying new roles to user {}: add: {}, remove: {}", userID, rolesToAdd, rolesToRemove);
        for (String role : rolesToRemove)
            removeRole(userID, role);
        for (String role : rolesToAdd)
            addRole(userID, role);
        return rolesToAdd.size() + rolesToRemove.size();
    }

    public static List<String> prettfiyRoleIDs(List<String> roleIDs) {
        return roleIDs.stream().map(r -> WordUtils.capitalize(r.replace("_", " "))).collect(Collectors.toList());
    }

    private static CredentialRepresentation createPasswordCredentials(String password) {
        CredentialRepresentation passwordCredentials = new CredentialRepresentation();
        passwordCredentials.setTemporary(false);
        passwordCredentials.setType(CredentialRepresentation.PASSWORD);
        passwordCredentials.setValue(password);
        return passwordCredentials;
    }
} @Service
public class OAuthClient {

    @Autowired
    private OAuthClientConfig config;

    @Autowired
    private OAuth2ClientContext oauth2Context;

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }

    public OAuth2AccessToken getToken(String username, String password) {

        // build resources
        ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
        resourceDetails.setClientId(config.getCliendId());
        resourceDetails.setClientSecret(config.getCliendSecret());
        resourceDetails.setAccessTokenUri(config.getAccessTokenUri());
        resourceDetails.setUsername(username);
        resourceDetails.setPassword(password);

        // build provider
        ResourceOwnerPasswordAccessTokenProvider accessTokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
        AccessTokenRequest accessTokenRequest = oauth2Context.getAccessTokenRequest();

        // fetch token
        OAuth2AccessToken accessToken = accessTokenProvider.obtainAccessToken(resourceDetails, accessTokenRequest);
        return accessToken;
    }

    public OAuth2AccessToken refreshToken(String refreshToken) {

        // build resources
        ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
        resourceDetails.setClientId(config.getCliendId());
        resourceDetails.setClientSecret(config.getCliendSecret());
        resourceDetails.setAccessTokenUri(config.getAccessTokenUri());

        // build provider
        AccessTokenRequest accessTokenRequest = oauth2Context.getAccessTokenRequest();
        ResourceOwnerPasswordAccessTokenProvider accessTokenProvider = new ResourceOwnerPasswordAccessTokenProvider();

        // perform refresh
        OAuth2AccessToken newAccessToken = accessTokenProvider.refreshAccessToken(resourceDetails, () -> refreshToken, accessTokenRequest);
        return newAccessToken;
    }

    public enum Role {
        NIMBLE_USER("nimble_user"),
        INITIAL_REPRESENTATIVE("initial_representative"),
        LEGAL_REPRESENTATIVE("legal_representative"),
        PLATFORM_MANAGER("platform_manager"),
        COMPANY_ADMIN("company_admin"),
        PUBLISHER("publisher"),
        NIMBLE_DELETED_USER("nimble_deleted_user"),
        EXTERNAL_REPRESENTATIVE("external_representative"),
        MONITOR("monitor"),
        EFACTORY_USER("eFactoryUser");

        private final String role;

        Role(final String role) {
            this.role = role;
        }

        @Override
        public String toString() {
            return role;
        }
    }
} public class DataModelUtils {

    private static final String circularEconomyCertificateGroup = "Circular Economy (Environment / Sustainability)";
    /**
     * UBL data model to Solr model converter
     */

    public static eu.nimble.service.model.solr.party.PartyType toIndexParty(eu.nimble.service.model.ubl.commonaggregatecomponents.PartyType party, QualifyingPartyType qualifyingParty) {
        PartyType indexParty = new PartyType();
        party.getBrandName().forEach(name -> indexParty.addBrandName(name.getLanguageID(), name.getValue()));
        if(party.getPartyName() != null && party.getPartyName().size() > 0){
            indexParty.setLegalName(party.getPartyName().get(0).getName().getValue());
        }
        // postal address
        if(party.getPostalAddress() != null){
            // country
            if(party.getPostalAddress().getCountry() != null && party.getPostalAddress().getCountry().getIdentificationCode() != null){
                Map<String ,String> countryNames = CountryUtil.getCountryNamesByISOCode(party.getPostalAddress().getCountry().getIdentificationCode().getValue());
                if(countryNames == null){
                    indexParty.addOrigin("en",party.getPostalAddress().getCountry().getIdentificationCode().getValue());
                } else {
                    countryNames.forEach(indexParty::addOrigin);
                }
            }
            // location longitude and latitude
            if(party.getPostalAddress().getCoordinate() != null && party.getPostalAddress().getCoordinate().getLatitude() != null && party.getPostalAddress().getCoordinate().getLongitude() != null){
                indexParty.setLocationLatitude(party.getPostalAddress().getCoordinate().getLatitude().doubleValue());
                indexParty.setLocationLongitude(party.getPostalAddress().getCoordinate().getLongitude().doubleValue());
            }
        }
        // when contact address set - include with party when indexing
        if ( party.getContact()!=null && party.getContact().getName() != null) {
        	ContactType contact = party.getContact();
        	
        	indexParty.setProperty(contact.getName().getValue(), "contact","Name");
        	indexParty.setProperty(contact.getElectronicMail(), "contact", "Email");
        	indexParty.setProperty(contact.getTelephone(), "contact", "Phone");
        }
        indexParty.setId(party.getHjid().toString());
        indexParty.setUri(party.getHjid().toString());

        // TODO currently we do not support multilingual certificate types
        indexParty.setCertificateType(getOtherCertificates(party));
        indexParty.setCircularEconomyCertificates(getCircularEconomyRelatedCertificateNames(party));
        if(party.getPpapCompatibilityLevel() != null) {
            indexParty.setPpapComplianceLevel(party.getPpapCompatibilityLevel().intValue());
        }
        // TODO currently we do not support multilingual ppap document types
        party.getPpapDocumentReference().forEach(ppapDocument -> indexParty.addPpapDocumentType("", ppapDocument.getDocumentType()));

        // get trust scores
        party.getQualityIndicator().forEach(qualityIndicator -> {
            if(qualityIndicator.getQualityParameter() != null && qualityIndicator.getQuantity() != null) {
                if(qualityIndicator.getQualityParameter().contentEquals(QualityIndicatorParameter.COMPANY_RATING.toString())) {
                    indexParty.setTrustRating(qualityIndicator.getQuantity().getValue().doubleValue());
                } else if(qualityIndicator.getQualityParameter().contentEquals(QualityIndicatorParameter.TRUST_SCORE.toString())) {
                    indexParty.setTrustScore(qualityIndicator.getQuantity().getValue().doubleValue());
                } else if(qualityIndicator.getQualityParameter().contentEquals(QualityIndicatorParameter.DELIVERY_PACKAGING.toString())) {
                    indexParty.setTrustDeliveryPackaging(qualityIndicator.getQuantity().getValue().doubleValue());
                } else if(qualityIndicator.getQualityParameter().contentEquals(QualityIndicatorParameter.FULFILLMENT_OF_TERMS.toString())) {
                    indexParty.setTrustFullfillmentOfTerms(qualityIndicator.getQuantity().getValue().doubleValue());
                } else if(qualityIndicator.getQualityParameter().contentEquals(QualityIndicatorParameter.SELLER_COMMUNICATION.toString())) {
                    indexParty.setTrustSellerCommunication(qualityIndicator.getQuantity().getValue().doubleValue());
                } else if(qualityIndicator.getQualityParameter().contentEquals(QualityIndicatorParameter.NUMBER_OF_TRANSACTIONS.toString())) {
                    indexParty.setTrustNumberOfTransactions(qualityIndicator.getQuantity().getValue().doubleValue());
                } else if(qualityIndicator.getQualityParameter().contentEquals(QualityIndicatorParameter.TRADING_VOLUME.toString())) {
                    indexParty.setTrustTradingVolume(qualityIndicator.getQuantity().getValue().doubleValue());
                }
            }
        });
        if (party.getIndustrySector() != null) {
            List<TextType> industrySectors = party.getIndustrySector();
            for (TextType sector : industrySectors) {
                String newLineChar = "\n";
                if (sector.getValue() != null) {
                    if (sector.getValue().contains(newLineChar)) {
                        String[] sectors = sector.getValue().split(newLineChar);
                        for (String sectorString : sectors) {
                            // remove carriage return
                            sectorString = sectorString.replace("\r","");
                            indexParty.addActivitySector(sector.getLanguageID(), sectorString);
                        }
                    } else {
                        indexParty.addActivitySector(sector.getLanguageID(), sector.getValue());
                    }
                }
            }
        }
        if (party.getIndustryClassificationCode() != null) {
            indexParty.setBusinessType(party.getIndustryClassificationCode().getValue());
        }
        //adding logo id
        if (party.getDocumentReference() != null) {
            for (DocumentReferenceType documentReference : party.getDocumentReference()) {
                if (UblAdapter.DOCUMENT_TYPE_COMPANY_LOGO.equals(documentReference.getDocumentType())) {
                    indexParty.setLogoId(documentReference.getHjid().toString());
                    break;
                }
            }
        }

        // business keywords
        for (TextType keyword : qualifyingParty.getBusinessClassificationScheme().getDescription()) {
            //check for line separators in the string
            String newLineChar = "\n";
            if (keyword.getValue() != null) {
                if (keyword.getValue().contains(newLineChar)) {
                    String[] keywords = keyword.getValue().split(newLineChar);
                    for (String keywordString : keywords) {
                        indexParty.addBusinessKeyword(keyword.getLanguageID(), keywordString);
                    }
                } else {
                    indexParty.addBusinessKeyword(keyword.getLanguageID(), keyword.getValue());
                }
            }
        }
        if(party.getWebsiteURI() != null) {
            indexParty.setWebsite(party.getWebsiteURI());
        }
        indexParty.setVerified(false);
        // set base platform
        indexParty.setBasePlatform(SpringBridge.getInstance().getFederatedIndexPlatformName());
        return indexParty;
    }

    private static Set<String> getCircularEconomyRelatedCertificateNames(eu.nimble.service.model.ubl.commonaggregatecomponents.PartyType partyType) {
        Set<String> certificateNames = new HashSet<>();
        partyType.getCertificate().stream()
                .filter(cert -> cert.getCertificateType().contentEquals(circularEconomyCertificateGroup))
                .forEach(cert -> {
                    certificateNames.add(cert.getCertificateTypeCode().getName());
                });
        return certificateNames;
    }

    private static Set<String> getOtherCertificates(eu.nimble.service.model.ubl.commonaggregatecomponents.PartyType partyType) {
        Set<String> certificateNames = new HashSet<>();
        partyType.getCertificate().stream()
                .filter(cert -> !cert.getCertificateType().contentEquals(circularEconomyCertificateGroup))
                .forEach(cert -> {
                    certificateNames.add(cert.getCertificateTypeCode().getName());
                });
        return certificateNames;
    }
} @Service
@PropertySource("classpath:bootstrap.yml")
public class IndexingClientController {

    private IndexingClient nimbleIndexClient;

    private IndexingClient federatedIndexClient;

    private List<IndexingClient> clients;

    @Value("${nimble.indexing.url}")
    private String nimbleIndexUrl;

    @Value("${federated-index-enabled}")
    private boolean federatedIndexEnabled;

    @Value("${nimble.indexing.federated-index-url}")
    private String federatedIndexUrl;

    @Value("${federated-index-platform-name}")
    private String federatedIndexPlatformName;

    @Autowired
    IndexingClientFallback indexingFallback;

    public String getFederatedIndexPlatformName() {
        return federatedIndexPlatformName;
    }

    public IndexingClientController() {

    }

    public IndexingClient getNimbleIndexClient() {
        if (nimbleIndexClient == null) {
            nimbleIndexClient = createIndexingClient(nimbleIndexUrl);
        }
        return nimbleIndexClient;
    }


    public IndexingClient getFederatedIndexClient() {
        if (federatedIndexEnabled && federatedIndexClient == null) {
            federatedIndexClient = createIndexingClient(federatedIndexUrl);
        }
        return federatedIndexClient;
    }

    public List<IndexingClient> getClients() {
        if (clients == null) {
            clients = new ArrayList<IndexingClient>();
            clients.add(getNimbleIndexClient());
            if (federatedIndexEnabled) {
                clients.add(getFederatedIndexClient());
            }
        }
        return clients;
    }

    private IndexingClient createIndexingClient(String url) {
        return HystrixFeign.builder().contract(new SpringMvcContract())
                .encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
                .retryer(new Retryer.Default(1,100,3))
                .target(IndexingClient.class, url, indexingFallback);
    }

} public enum LogEvent {
    LOGIN_ATTEMPT("loginAttempt"), LOGIN_SUCCESS("loginSuccess"), LOGIN_ERROR("loginError"),
    REGISTER_USER("registerUser"), REGISTER_USER_ERROR("registerUserError"), REGISTER_COMPANY("registerCompany"), UPDATE_COMPANY("updateCompany"),
    VERIFY_COMPANY("verifyCompany"), DELETE_COMPANY("deleteCompany") , REJECT_COMPANY("rejectCompany"), DELETE_USER("deleteUser"), REVERT_COMPANY("revertCompany"),
    INVITED_USER_REGISTRATION("invitedUserRegistration");

    private String activity;

    LogEvent(String activity){
        this.activity = activity;
    }

    public String getActivity(){
        return activity;
    }
}

import eu.nimble.core.infrastructure.identity.clients.BusinessProcessServiceClient;
import eu.nimble.core.infrastructure.identity.clients.CatalogueServiceClient;
import eu.nimble.core.infrastructure.identity.clients.IndexingClient;
import eu.nimble.core.infrastructure.identity.constants.GlobalConstants;
import eu.nimble.core.infrastructure.identity.entity.NegotiationSettings;
import eu.nimble.core.infrastructure.identity.entity.UaaUser;
import eu.nimble.core.infrastructure.identity.entity.UserInvitation;
import eu.nimble.core.infrastructure.identity.mail.EmailService;
import eu.nimble.core.infrastructure.identity.messaging.KafkaSender;
import eu.nimble.core.infrastructure.identity.repository.*;
import eu.nimble.core.infrastructure.identity.service.AdminService;
import eu.nimble.core.infrastructure.identity.service.IdentityService;
import eu.nimble.core.infrastructure.identity.service.RocketChatService;
import eu.nimble.core.infrastructure.identity.uaa.KeycloakAdmin;
import eu.nimble.core.infrastructure.identity.uaa.OAuthClient;
import eu.nimble.core.infrastructure.identity.utils.DataModelUtils;
import eu.nimble.core.infrastructure.identity.clients.IndexingClientController;
import eu.nimble.core.infrastructure.identity.utils.LogEvent;
import eu.nimble.service.model.solr.Search;
import eu.nimble.service.model.ubl.commonaggregatecomponents.PartyType;
import eu.nimble.service.model.ubl.commonaggregatecomponents.PersonType;
import eu.nimble.service.model.ubl.commonaggregatecomponents.QualifyingPartyType;
import eu.nimble.utility.ExecutionContext;
import eu.nimble.utility.LoggerUtils;
import eu.nimble.utility.bp.BusinessWorkflowUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.stream.Collectors;
import static eu.nimble.core.infrastructure.identity.uaa.OAuthClient.Role.PLATFORM_MANAGER;
package eu.nimble.core.infrastructure.identity.system;




/**
 * Created by Johannes Innerbichler on 12.09.18.
 */
@Controller
@RequestMapping(path = "/admin")
@Api(value = "Admin API", description = "Administration services for managing identity on the platform.")
public class AdminController {

    private static final Logger logger = LoggerFactory.getLogger(AdminController.class);

    private static final String DEFAULT_PAGE_SIZE = "10";

    @Autowired
    private AdminService adminService;