class JobDetailFormatter {

    protected static String buildDetails(JobValidator validator, Workload workload, JobInstance proposedJobInstance,
            String scriptName) {
        StringBuilder sb = new StringBuilder();
        StringBuilder errorSB = new StringBuilder();
        TankConfig config = new TankConfig();
        if (proposedJobInstance != null) {

            if (StringUtils.isBlank(proposedJobInstance.getName())) {
                addError(errorSB, "Name cannot be null");
            }

            JobRegionDao jrd = new JobRegionDao();
            List<JobRegion> regions = new ArrayList<JobRegion>();
            for (EntityVersion ver : proposedJobInstance.getJobRegionVersions()) {
                JobRegion region = jrd.findById(ver.getObjectId());
                if (region != null) {
                    long users = TestParamUtil.evaluateExpression(region.getUsers(),
                            proposedJobInstance.getExecutionTime(),
                            proposedJobInstance.getSimulationTime(), proposedJobInstance.getRampTime());
                    if (users > 0) {
                        regions.add(new JobRegion(region.getRegion(), Long.toString(users)));
                    }
                }
            }
            Collections.sort(regions);
            long simulationTime = getSimulationTime(proposedJobInstance, workload, validator);
            addProperty(sb, "General Information", "",
                    "emphasis");
            addProperty(sb, "Name", StringUtils.isBlank(proposedJobInstance.getName()) ? "Name cannot be null"
                    : proposedJobInstance.getName(), StringUtils.isBlank(proposedJobInstance.getName()) ? "error"
                    : null);
            addProperty(sb, "Workload Type", proposedJobInstance.getIncrementStrategy().name());
            addProperty(sb, "Tank Http Client", config.getAgentConfig().getTankClientName(proposedJobInstance.getTankClientClass()));
            addProperty(sb, "Agent VM Type", getVmDetails(config, proposedJobInstance.getVmInstanceType()));
            addProperty(sb, "Assign Elastic Ips", Boolean.toString(proposedJobInstance.isUseEips()));
            addProperty(sb, "Max Users per Agent", Integer.toString(proposedJobInstance.getNumUsersPerAgent()));
            addProperty(sb, "Estimated Cost", calculateCost(config, proposedJobInstance, regions, simulationTime));
            addProperty(sb, "Location", proposedJobInstance.getLocation());
            addProperty(sb, "Logging Profile", LoggingProfile.fromString(proposedJobInstance.getLoggingProfile())
                    .getDisplayName());
            addProperty(sb, "Stop Behavior", StopBehavior.fromString(proposedJobInstance.getStopBehavior())
                    .getDisplay());
            addProperty(sb, "Run Scripts Until", proposedJobInstance.getTerminationPolicy().getDisplay(),
                    proposedJobInstance.getTerminationPolicy() == TerminationPolicy.time
                            && proposedJobInstance.getSimulationTime() == 0 ? "error" : null);
            sb.append(BREAK);
            addProperty(
                    sb,
                    "Simulation Time",
                    TimeUtil.toTimeString(simulationTime));
            if (proposedJobInstance.getTerminationPolicy() == TerminationPolicy.time
                    && proposedJobInstance.getSimulationTime() == 0) {
                addError(errorSB, "Simulation time not set.");
            }
            addProperty(sb, "Ramp Time", TimeUtil.toTimeString(proposedJobInstance.getRampTime()));
            addProperty(sb, "Initial Users", Integer.toString(proposedJobInstance.getBaselineVirtualUsers()));
            addProperty(sb, "User Increment", Integer.toString(proposedJobInstance.getUserIntervalIncrement()));
            // users and regions
            sb.append(BREAK);
            addProperty(sb, "Total Users", Integer.toString(proposedJobInstance.getTotalVirtualUsers()),
                    proposedJobInstance.getTotalVirtualUsers() == 0 ? "error" : "emphasis");
            if (proposedJobInstance.getTotalVirtualUsers() == 0) {
                addError(errorSB, "No users defined.");
            }

            for (JobRegion r : regions) {
                if (config.getStandalone()) {
                    addProperty(sb, "  Users", r.getUsers());
                } else {
                    addProperty(sb, "  " + r.getRegion().getDescription(), r.getUsers());
                }
            }
            sb.append(BREAK);
            sb.append(BREAK);

            int userPercentage = workload.getTestPlans().stream().mapToInt(TestPlan::getUserPercentage).sum();
            if (userPercentage != 100) {
                addError(errorSB, "User Percentage of Test Plans does not add up to 100%");
            }
            // datafiles
            addProperty(sb, "Data Files", proposedJobInstance.getDataFileVersions().size() == 0 ? "None" : null,
                    "emphasis");
            DataFileDao dfd = new DataFileDao();
            Set<String> datafiles = new HashSet<String>();
            for (EntityVersion ver : proposedJobInstance.getDataFileVersions()) {
                DataFile df = dfd.findById(ver.getObjectId());
                if (df != null) {
                    addProperty(sb, "  " + df.getPath(), null);
                    datafiles.add(df.getPath());
                } else {
                    addProperty(sb, "  " + ver.getObjectId(), "data file not found.", "error");
                }
            }
            sb.append(BREAK);

            // variables
            addProperty(sb, "Global Variables", proposedJobInstance.getVariables().size() == 0 ? "None"
                    : " (Allow Overide: "
                            + proposedJobInstance.isAllowOverride() + ")", "emphasis");
            for (Entry<String, String> entry : proposedJobInstance.getVariables().entrySet()) {
                addProperty(sb, "  " + entry.getKey(), entry.getValue());
                if (entry.getValue().toLowerCase().endsWith(".csv") && !datafiles.contains(entry.getValue())) {
                    addProperty(sb, "  WARNING", "This variable, " + entry.getKey()
                            + " appears to be a reference to a datafile, " + entry.getValue()
                            + " that is not declared.", "error");
                }
            }
            sb.append(BREAK);

            // notifications
            addProperty(sb, "Notifications", proposedJobInstance.getNotificationVersions().size() == 0 ? "None" : null,
                    "emphasis");
            JobNotificationDao jnd = new JobNotificationDao();
            for (EntityVersion ver : proposedJobInstance.getNotificationVersions()) {
                JobNotification not = jnd.findById(ver.getObjectId());
                if (not != null) {
                    if (not.getLifecycleEvents().size() > 0) {
                        addProperty(sb, "  " + not.getRecipientList(), StringUtils.join(not.getLifecycleEvents(), ", "));
                    } else {
                        addProperty(sb, "  " + not.getRecipientList(), "no events selected", "error");
                    }
                }
            }
            sb.append(BREAK);
            addProperty(sb, "Scripts", "", "emphasis");
            // scripts
            List<ScriptGroupStep> stepsList = new ArrayList<ScriptGroupStep>();
            for (TestPlan plan : workload.getTestPlans()) {
                int numUsers = plan.getUserPercentage() > 0 ? proposedJobInstance.getTotalVirtualUsers() : 0;
                if (plan.getUserPercentage() < 100 && plan.getUserPercentage() > 0) {
                    numUsers = (int) Math.floor(numUsers * ((double) plan.getUserPercentage() / 100D));
                }
                addProperty(
                        sb,
                        "  " + plan.getName(),
                        plan.getUserPercentage() + "% : (" + numUsers
                                + " users) : estimated Time "
                                + TimeUtil.toTimeString(validator.getExpectedTime(plan.getName())),
                        userPercentage != 100 ? "error" : null);

                if (plan.getScriptGroups().size() == 0) {
                    addProperty(sb, "  " + plan.getName(), "contains no script groups", "error");
                }
                for (ScriptGroup group : plan.getScriptGroups()) {
                    addProperty(sb, "    " + group.getName(), "loop " + group.getLoop() + " time(s)");
                    if (group.getScriptGroupSteps().size() == 0) {
                        addProperty(sb, "    " + group.getName(), "contains no scripts", "error");
                    }
                    for (ScriptGroupStep s : group.getScriptGroupSteps()) {
                        stepsList.add(s);
                        addProperty(sb, "      " + s.getScript().getName(), "loop " + s.getLoop() + " time(s)");
                    }
                }
            }
            sb.append(BREAK);
            if (stepsList.size() == 0) {
                addError(errorSB, "No scripts defined.");
            }

        } else {
            addProperty(sb, scriptName, "Estimated Time " + validator.getDuration(scriptName), "emphasis");
            sb.append(BREAK);
            sb.append(BREAK);
        }
        addProperty(sb, "Variable Validation", "", "emphasis");
        addProperty(sb, "  Declared Variables", "", "emphasis");
        for (Entry<String, Set<String>> entry : validator.getDeclaredVariables().entrySet()) {
            for (String value : entry.getValue()) {
                addProperty(sb, "    " + entry.getKey(), value,
                        validator.isSuperfluous(entry.getKey()) ? "error" : null);
            }
        }
        sb.append(BREAK);
        addProperty(sb, "  Used Variables", "", "emphasis");
        for (String s : validator.getUsedVariables()) {
            addProperty(sb, "    ", s, validator.isOrphaned(s) ? "error" : null);
        }
        sb.append(BREAK);
        if (validator.isProcessAssignements()) {
            addProperty(sb, "  Assignements", "", "emphasis");
            for (Entry<String, Set<String>> entry : validator.getAssignments().entrySet()) {
                for (String value : entry.getValue()) {
                    addProperty(sb, "    " + entry.getKey(), value,
                            validator.isSuperfluous(entry.getKey()) ? "error" : null);
                }
            }
            sb.append(BREAK);
        }
        sb.append(BREAK);
        if (!validator.getBestPracticeViolations().isEmpty()) {
            StringBuilder tsb = new StringBuilder();
            addProperty(tsb, "Best Practice Violations", "", "emphasis");
            for (String s : validator.getBestPracticeViolations()) {
                addError(tsb, s);
            }
            tsb.append(BREAK);
            tsb.append(BREAK);
            sb.insert(0, tsb.toString());
        }

        if (errorSB.length() > 0) {
            sb = new StringBuilder().append("ERRORS").append(BREAK).append(errorSB.append(BREAK).toString())
                    .append(sb.toString());
        }
        return sb.toString();
    }

}