@FilterWith({XSRFFilter.class, AdminFilter.class})
  public Result delete(Context context) {
    FlashScope flash = context.getFlashScope();
    Group group = context.getAttribute("group", Group.class);

    // TODO FIX ME locking until database modification done
    if (taskManager.isGoogleRunning()) {
      flash.error("admin.google.errorTaskRunning");
      return Results.redirect(
          router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId()));
    }

    List<GoogleTarget> targets = googleDB.target.list(Arrays.asList(group.getId()));
    for (GoogleTarget target : targets) {
      googleDB.targetSummary.deleteByTarget(target.getId());
      googleDB.rank.deleteByTarget(group.getId(), target.getId());
      googleDB.target.delete(target.getId());
    }

    List<GoogleSearch> searches = googleDB.search.listByGroup(Arrays.asList(group.getId()));
    for (GoogleSearch search : searches) {
      deleteSearch(group, search);
    }

    baseDB.event.delete(group);
    baseDB.user.delPerm(group);
    if (!baseDB.group.delete(group)) {
      flash.error("admin.google.failedDeleteGroup");
      return Results.redirect(
          router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId()));
    } else {
      flash.success("admin.google.groupDeleted");
      return Results.redirect(router.getReverseRoute(GroupController.class, "groups"));
    }
  }
  @FilterWith({XSRFFilter.class, AdminFilter.class})
  public Result delSearch(Context context, @Params("id[]") String[] ids) {

    FlashScope flash = context.getFlashScope();
    Group group = context.getAttribute("group", Group.class);

    if (ids == null || ids.length == 0) {
      flash.error("error.noSearchSelected");
      return Results.redirect(
          router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId()));
    }

    List<GoogleSearch> searches = new ArrayList<>();
    for (String id : ids) {
      GoogleSearch search = null;
      try {
        search = getSearch(context, Integer.parseInt(id));
      } catch (Exception ex) {
        search = null;
      }

      if (search == null) {
        flash.error("error.invalidSearch");
        return Results.redirect(
            router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId()));
      }

      searches.add(search);
    }

    // TODO FIX ME locking until database modification done
    if (taskManager.isGoogleRunning()) {
      flash.error("admin.google.errorTaskRunning");
      return Results.redirect(
          router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId()));
    }

    for (GoogleSearch search : searches) {
      deleteSearch(group, search);
    }

    return Results.redirect(
        router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId())
            + "#tab-searches");
  }
  @FilterWith({XSRFFilter.class, AdminFilter.class})
  public Result delTarget(Context context, @Params("id[]") String[] ids) {
    FlashScope flash = context.getFlashScope();
    Group group = context.getAttribute("group", Group.class);

    // TODO FIX ME locking until database modification done
    if (taskManager.isGoogleRunning()) {
      flash.error("admin.google.errorTaskRunning");
      return Results.redirect(
          router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId()));
    }

    if (ids == null || ids.length == 0) {
      flash.error("error.noWebsiteSelected");
      return Results.redirect(
          router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId()));
    }

    for (String id : ids) {
      GoogleTarget target = null;
      try {
        target = getTarget(context, Integer.parseInt(id));
      } catch (Exception ex) {
        target = null;
      }

      if (target == null) {
        flash.error("error.invalidWebsite");
        return Results.redirect(
            router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId()));
      }

      googleDB.targetSummary.deleteByTarget(target.getId());
      googleDB.rank.deleteByTarget(group.getId(), target.getId());
      googleDB.target.delete(target.getId());
    }

    return Results.redirect(
        router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId()));
  }
  @FilterWith({XSRFFilter.class, AdminFilter.class})
  public Result addTarget(
      Context context,
      @Param("target-radio") String targetType,
      @Params("name[]") String[] names,
      @Params("pattern[]") String[] patterns) {
    FlashScope flash = context.getFlashScope();
    Group group = context.getAttribute("group", Group.class);

    if (targetType == null
        || names == null
        || names.length == 0
        || patterns == null
        || patterns.length == 0
        || names.length != patterns.length) {
      flash.error("error.invalidParameters");
      return Results.redirect(
          router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId()));
    }

    Set<GoogleTarget> targets = new HashSet<>();
    for (int i = 0; i < names.length; i++) {
      String name = names[i];
      String pattern = patterns[i];

      if (name != null) {
        name = name.replaceAll("(^\\s+)|(\\s+$)", "");
      }

      if (pattern != null) {
        pattern = pattern.replaceAll("(^\\s+)|(\\s+$)", "");
      }

      if (Validator.isEmpty(name)) {
        flash.error("error.invalidName");
        return Results.redirect(
            router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId()));
      }

      PatternType type = null;
      try {
        type = PatternType.valueOf(targetType);
      } catch (Exception ex) {
        flash.error("error.invalidTargetType");
        return Results.redirect(
            router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId()));
      }

      if (PatternType.DOMAIN.equals(type) || PatternType.SUBDOMAIN.equals(type)) {
        try {
          pattern = IDN.toASCII(pattern);
        } catch (Exception ex) {
          pattern = null;
        }
      }

      if (!GoogleTarget.isValidPattern(type, pattern)) {
        flash.error("error.invalidPattern");
        return Results.redirect(
            router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId()));
      }

      targets.add(new GoogleTarget(group.getId(), name, type, pattern));
    }

    if (googleDB.target.insert(targets) < 1) {
      flash.error("error.internalError");
      return Results.redirect(
          router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId()));
    }
    googleDB.serpRescan.rescan(null, targets, getSearches(context), true);

    Run runningGoogleTask = taskManager.getRunningGoogleTask();
    if (runningGoogleTask != null) {
      flash.put(
          "warning",
          msg.get(
                  "google.group.websiteInsertedWhileRun",
                  context,
                  Optional.absent(),
                  runningGoogleTask.getId())
              .or(""));
    } else {
      flash.success("google.group.websiteInserted");
    }

    return Results.redirect(
        router.getReverseRoute(GoogleGroupController.class, "view", "groupId", group.getId()));
  }
  @FilterWith(XSRFFilter.class)
  public Result dryRun(
      Context context, @Param("startDate") String start, @Param("endDate") String end) {
    long _start = System.currentTimeMillis();
    FlashScope flash = context.getFlashScope();

    LocalDate startDate = null;
    LocalDate endDate = null;

    try {
      startDate = LocalDate.parse(start);
      endDate = LocalDate.parse(end);
    } catch (Exception ex) {
    }

    if (startDate == null || endDate == null || startDate.isAfter(endDate)) {
      flash.error("error.invalidDate");
      return Results.redirect(router.getReverseRoute(DebugController.class, "debug"));
    }

    Run lastRun = baseDB.run.findLast(Module.GOOGLE, null, null);
    if (lastRun != null && lastRun.getDay().isAfter(startDate)) {
      flash.error("error.invalidDate");
      return Results.redirect(router.getReverseRoute(DebugController.class, "debug"));
    }

    LocalDate date = LocalDate.from(startDate);

    GoogleSettings ggOptions = googleDB.options.get();

    int minPauseBetweenPageSec = ggOptions.getMinPauseBetweenPageSec();
    int maxPauseBetweenPageSec = ggOptions.getMaxPauseBetweenPageSec();
    ggOptions.setMinPauseBetweenPageSec(0);
    ggOptions.setMaxPauseBetweenPageSec(0);
    googleDB.options.update(ggOptions);

    try {
      while (date.isBefore(endDate)) {
        LOG.debug("dry run {}", date);
        if (!taskManager.startGoogleTask(
            new Run(Run.Mode.MANUAL, Module.GOOGLE, date.atTime(13, 37, 00)))) {
          LOG.error("can't startGoogleTask");
          flash.error("can't startGoogleTask");
          return Results.redirect(router.getReverseRoute(DebugController.class, "debug"));
        }
        taskManager.joinGoogleTask();
        date = date.plusDays(1);
      }
    } catch (Exception ex) {
      LOG.error("an error occured", ex);
      flash.error("an error occured");
      return Results.redirect(router.getReverseRoute(DebugController.class, "debug"));
    } finally {
      ggOptions.setMinPauseBetweenPageSec(minPauseBetweenPageSec);
      ggOptions.setMaxPauseBetweenPageSec(maxPauseBetweenPageSec);
      googleDB.options.update(ggOptions);
    }

    LOG.debug(
        "dry run timing : {}",
        DurationFormatUtils.formatDurationHMS(System.currentTimeMillis() - _start));
    flash.success("ok");
    return Results.redirect(router.getReverseRoute(DebugController.class, "debug"));
  }