
package jetbrains.buildServer.usageStatistics.impl.providers;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Set;
import jetbrains.buildServer.serverSide.BuildServerEx;
import jetbrains.buildServer.serverSide.db.queries.GenericQuery;
import jetbrains.buildServer.serverSide.impl.CompositeRunningBuild;
import jetbrains.buildServer.usageStatistics.UsageStatisticsPublisher;
import jetbrains.buildServer.usageStatistics.presentation.UsageStatisticsFormatter;
import jetbrains.buildServer.usageStatistics.presentation.UsageStatisticsGroupPosition;
import jetbrains.buildServer.usageStatistics.presentation.UsageStatisticsPresentationManager;
import jetbrains.buildServer.usageStatistics.presentation.formatters.PercentageFormatter;
import jetbrains.buildServer.usageStatistics.presentation.formatters.TimeFormatter;
import jetbrains.buildServer.util.CollectionsUtil;
import jetbrains.buildServer.util.positioning.PositionAware;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class ServerLoadUsageStatisticsProvider extends BaseDynamicUsageStatisticsProvider {

  @NotNull
  private static final UsageStatisticsFormatter ourTimeFormatter = new TimeFormatter();

  @NotNull
  private static final GenericQuery<Void> ourCompositeBuildsQuery = new GenericQuery<>(
    "select count(h.build_id) as build_count" +
    " from (" +
    " select history.build_id from history where build_finish_time_server > ? and agent_name like '" + CompositeRunningBuild.FAKE_SERVER_AGENT + "'" +
    " union all" +
    " select build_id from removed_builds_history where build_finish_time_server > ? and agent_name like '" + CompositeRunningBuild.FAKE_SERVER_AGENT + "'" +
    " union all" +
    " select build_id from light_history where build_finish_time_server > ? and agent_name like '" + CompositeRunningBuild.FAKE_SERVER_AGENT + "'" +
    ") h"
  );

  @NotNull
  private static final GenericQuery<Void> ourRegularBuildsQuery = new GenericQuery<>(
    "select" +
    " count(h.build_id) as build_count," +
    " sum(h.is_personal) as personal_build_count," +
    " avg(h.remove_from_queue_time - h.queued_time) as avg_build_queue_time," +
    " avg(h.build_finish_time_server - h.build_start_time_server) as avg_build_duration" +
    " from (" +
    " select history.build_id, history.is_personal,build_finish_time_server,build_start_time_server, history.remove_from_queue_time as remove_from_queue_time, history.queued_time as queued_time" +
    " from history where build_finish_time_server > ? and agent_name not like '" + CompositeRunningBuild.FAKE_SERVER_AGENT + "'" +
    " union all" +
    " select build_id, is_personal,build_finish_time_server,build_start_time_server, remove_from_queue_time, queued_time" +
    " from removed_builds_history" +
    " where build_finish_time_server > ? and agent_name not like '" + CompositeRunningBuild.FAKE_SERVER_AGENT + "' " +
    " union all" +
    " select build_id, is_personal,build_finish_time_server,build_start_time_server, remove_from_queue_time, queued_time" +
    " from light_history" +
    " where build_finish_time_server > ? and agent_name not like '" + CompositeRunningBuild.FAKE_SERVER_AGENT + "' " +
    ") h"
  );

  @NotNull
  private static final GenericQuery<Void> ourBuildTestCountQuery = new GenericQuery<>(
    "select" +
    " max(s.test_count) as max_test_count" +
    " from stats s" +
    " inner join history h on s.build_id = h.build_id" +
    " where h.build_finish_time_server > ? and agent_name not like '" + CompositeRunningBuild.FAKE_SERVER_AGENT + "'"
  );

  @NotNull
  private static final GenericQuery<Void> ourVcsChangesCountQuery = new GenericQuery<>(
    "select count(*) as vcs_changes_count" +
    " from vcs_history h" +
    " where register_date > ?"
  );

  @NotNull private final BuildServerEx myServer;
  @NotNull private final WebUsersProvider myWebUsersProvider;
  @NotNull private final IDEUsersProvider myIDEUsersProvider;

  public ServerLoadUsageStatisticsProvider(@NotNull final BuildServerEx server,
                                           @NotNull final WebUsersProvider webUsersProvider,
                                           @NotNull final IDEUsersProvider ideUsersProvider) {
    super(createDWMPeriodDescriptions(), null);
    myServer = server;
    myWebUsersProvider = webUsersProvider;
    myIDEUsersProvider = ideUsersProvider;
  }

  @NotNull
  @Override
  protected PositionAware getGroupPosition() {
    return UsageStatisticsGroupPosition.SERVER_LOAD;
  }

  @Override
  protected void accept(@NotNull final UsageStatisticsPublisher publisher,
                        @NotNull final UsageStatisticsPresentationManager presentationManager,
                        @NotNull final String periodDescription,
                        final long startDate) {
    publishBuildData(publisher, presentationManager, periodDescription, startDate);
    publishOnlineUsers(publisher, presentationManager, periodDescription, startDate);
    publishVcsChanges(publisher, presentationManager, periodDescription, startDate);
  }

  private void publishBuildData(@NotNull final UsageStatisticsPublisher publisher,
                                @NotNull final UsageStatisticsPresentationManager presentationManager,
                                @NotNull final String periodDescription,
                                final long fromDate) {
    apply(presentationManager, periodDescription, "buildCount", "Build count", null, null);
    apply(presentationManager, periodDescription, "compositeBuildCount", "Composite build count", null, null);
    apply(presentationManager, periodDescription, "personalBuildCount", "Personal build count", null, null);
    apply(presentationManager, periodDescription, "avgBuildWaitInQueueTime", "Average build waiting in queue time", ourTimeFormatter, null);
    apply(presentationManager, periodDescription, "avgBuildDuration", "Average build duration", ourTimeFormatter, null);
    apply(presentationManager, periodDescription, "maxBuildTestCount", "Maximum test count per build", null, null);

    ourRegularBuildsQuery.execute(myServer.getSQLRunner(), rs -> {
      if (rs.next()) {
        publish(publisher, periodDescription, "buildCount", rs.getLong(1));
        publish(publisher, periodDescription, "personalBuildCount", rs.getLong(2));
        publish(publisher, periodDescription, "avgBuildWaitInQueueTime", getNullableLong(rs, 3));
        publish(publisher, periodDescription, "avgBuildDuration", getNullableLong(rs, 4));
      }
      return null;
    }, fromDate, fromDate, fromDate);

    ourCompositeBuildsQuery.execute(myServer.getSQLRunner(), rs -> {
      if (rs.next()) {
        publish(publisher, periodDescription, "compositeBuildCount", rs.getLong(1));
      }
      return null;
    }, fromDate, fromDate, fromDate);

    ourBuildTestCountQuery.execute(myServer.getSQLRunner(), rs -> {
      if (rs.next()) {
        publish(publisher, periodDescription, "maxBuildTestCount", getNullableLong(rs, 1));
      }
      return null;
    }, fromDate);
  }

  private void publishOnlineUsers(@NotNull final UsageStatisticsPublisher publisher,
                                  @NotNull final UsageStatisticsPresentationManager presentationManager,
                                  @NotNull final String periodDescription,
                                  final long fromDate) {
    final String webUsersId = "webUsers";
    final String ideUsersId = "ideUsers";
    final String webOnlyUsersId = "webOnlyUsers";
    final String ideOnlyUsersId = "ideOnlyUsers";

    final UsageStatisticsFormatter formatter = new PercentageFormatter(myServer.getUserModel().getNumberOfRegisteredUsers());
    final String valueTooltip = "User count (% of all users)";

    apply(presentationManager, periodDescription, webUsersId, "Web users", formatter, valueTooltip);
    apply(presentationManager, periodDescription, ideUsersId, "IDE users", formatter, valueTooltip);
    apply(presentationManager, periodDescription, webOnlyUsersId, "Web only users", formatter, valueTooltip);
    apply(presentationManager, periodDescription, ideOnlyUsersId, "IDE only users", formatter, valueTooltip);

    final Set<String> webUsers = myWebUsersProvider.getWebUsers(fromDate);
    final Set<String> ideUsers = myIDEUsersProvider.getIDEUsers(fromDate);

    publish(publisher, periodDescription, webUsersId, webUsers.size());
    publish(publisher, periodDescription, ideUsersId, ideUsers.size());
    publish(publisher, periodDescription, webOnlyUsersId, CollectionsUtil.minus(webUsers, ideUsers).size());
    publish(publisher, periodDescription, ideOnlyUsersId, CollectionsUtil.minus(ideUsers, webUsers).size());
  }

  private void publishVcsChanges(@NotNull final UsageStatisticsPublisher publisher,
                                 @NotNull final UsageStatisticsPresentationManager presentationManager,
                                 @NotNull final String periodDescription,
                                 final long fromDate) {
    final String vcsChangesId = "vcsChanges";
    apply(presentationManager, periodDescription, vcsChangesId, "VCS changes", null, null);
    ourVcsChangesCountQuery.execute(myServer.getSQLRunner(), rs -> {
      if (rs.next()) {
        publish(publisher, periodDescription, vcsChangesId, rs.getInt(1));
      }
      return null;
    }, fromDate);
  }

  @Override
  protected boolean mustSortStatistics() {
    return false;
  }

  @Nullable
  private Long getNullableLong(final ResultSet rs, final int index) throws SQLException {
    final long value = rs.getLong(index);
    return rs.wasNull() ? null : value;
  }

  private void apply(@NotNull final UsageStatisticsPresentationManager presentationManager,
                     @NotNull final String periodDescription,
                     @NotNull final String id,
                     @NotNull final String name,
                     @Nullable final UsageStatisticsFormatter formatter,
                     @Nullable final String valueTooltip) {
    presentationManager.applyPresentation(makeId(id, periodDescription), name, myGroupName, formatter, valueTooltip);
  }

  private void publish(@NotNull final UsageStatisticsPublisher publisher,
                       @NotNull final String periodDescription,
                       @NotNull final String id,
                       @Nullable final Object value) {
    publisher.publishStatistic(makeId(id, periodDescription), value);
  }
}