# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
package Bugzilla::Extension::Needinfo;

use 5.10.1;
use strict;
use warnings;

use base qw(Bugzilla::Extension);

use Bugzilla::Error;
use Bugzilla::Flag;
use Bugzilla::FlagType;
use Bugzilla::User;
use Bugzilla::User::Setting;

our $VERSION = '0.01';

BEGIN {
  *Bugzilla::User::needinfo_blocked = \&_user_needinfo_blocked;
}

sub _user_needinfo_blocked {
  return $_[0]->settings->{block_needinfo}->{value} eq 'on';
}

sub install_update_db {
  my ($self, $args) = @_;
  my $dbh = Bugzilla->dbh;

  if (@{Bugzilla::FlagType::match({name => 'needinfo'})}) {
    return;
  }

  print "Creating needinfo flag ... "
    . "enable the Needinfo feature by editing the flag's properties.\n";

  # inclusions 0:0 maps to __ANY__ : __ANY__ in the UI,
  # meaning needinfo is enabled for all products and components by default
  my $flagtype = Bugzilla::FlagType->create({
    name => 'needinfo',
    description =>
      "Set this flag when the bug is in need of additional information",
    target_type      => 'bug',
    cc_list          => '',
    sortkey          => 1,
    is_active        => 1,
    is_requestable   => 1,
    is_requesteeble  => 1,
    is_multiplicable => 0,
    request_group    => '',
    grant_group      => '',
    inclusions       => ['0:0'],
    exclusions       => [],
  });
}

sub install_before_final_checks {
  my ($self, $args) = @_;
  add_setting({
    name     => 'block_needinfo',
    options  => ['on', 'off'],
    default  => 'off',
    category => 'Reviews and Needinfo'
  });
}

sub bug_end_of_create {
  my ($self, $args) = @_;
  my $params = Bugzilla->input_params;

  return unless $params->{needinfo_from};

  # Add extra params
  $params->{needinfo} = 1;
  $params->{needinfo_role} = 'other';

  # Do the rest
  $self->bug_start_of_update($args);
}

# Clear the needinfo? flag if comment is being given by
# requestee or someone used the override flag.
sub bug_start_of_update {
  my ($self, $args) = @_;
  my $bug     = $args->{bug};
  my $old_bug = $args->{old_bug};

  my $user   = Bugzilla->user;
  my $cgi    = Bugzilla->cgi;
  my $params = Bugzilla->input_params;

  if ($params->{needinfo}) {

    # do a match if applicable
    Bugzilla::User::match_field({'needinfo_from' => {'type' => 'multi'}});
  }

  # Set needinfo_done param to true so as to not loop back here
  return if $params->{needinfo_done};
  $params->{needinfo_done} = 1;
  Bugzilla->input_params($params);

  my $add_needinfo  = delete $params->{needinfo};
  my $needinfo_type = delete $params->{needinfo_type} // '';
  my $needinfo_from = delete $params->{needinfo_from};
  my $needinfo_role = delete $params->{needinfo_role};
  my $is_redirect   = $needinfo_type eq 'redirect_to' ? 1 : 0;
  my $is_private    = $params->{'comment_is_private'};

  my @needinfo_overrides;
  foreach my $key (grep(/^needinfo_override_/, keys %$params)) {
    my ($id) = $key =~ /(\d+)$/;

    # Should always be true if key exists (checkbox) but better to be sure
    push(@needinfo_overrides, $id) if $id && $params->{$key};
  }

  # Set the needinfo flag if user is requesting more information
  my @new_flags;
  my $needinfo_requestee;

  if ($add_needinfo) {
    foreach my $type (@{$bug->flag_types}) {
      next if $type->name ne 'needinfo';
      my %requestees;

      # Allow anyone to be the requestee
      if (!$needinfo_role) {
        $requestees{'anyone'} = 1;
      }

      # Use assigned_to as requestee
      elsif ($needinfo_role eq 'assigned_to') {
        $requestees{$bug->assigned_to->login} = 1;
      }

      # Use reporter as requestee
      elsif ($needinfo_role eq 'reporter') {
        $requestees{$bug->reporter->login} = 1;
      }

      # Use qa_contact as requestee
      elsif ($needinfo_role eq 'qa_contact') {
        $requestees{$bug->qa_contact->login} = 1;
      }

      # Use current user as requestee
      elsif ($needinfo_role eq 'user') {
        $requestees{$user->login} = 1;
      }
      elsif ($needinfo_role eq 'triage_owner') {
        if ($bug->component_obj->triage_owner_id) {
          $requestees{$bug->component_obj->triage_owner->login} = 1;
        }
      }

      # Use user specified requestee
      elsif ($needinfo_role eq 'other' && $needinfo_from) {
        my @needinfo_from_list
          = ref $needinfo_from ? @$needinfo_from : ($needinfo_from);
        foreach my $requestee (@needinfo_from_list) {
          my $requestee_obj = Bugzilla::User->check($requestee);
          $requestees{$requestee_obj->login} = 1;
        }
      }

      # Requestee is a mentor
      elsif ($needinfo_role
        && Bugzilla::User->check({name => $needinfo_role, cache => 1}))
      {
        $requestees{$needinfo_role} = 1;
      }

      # Find out if the requestee has already been used and skip if so
      my $requestee_found;
      foreach my $flag (@{$type->{flags}}) {
        if (!$flag->requestee && $requestees{'anyone'}) {
          delete $requestees{'anyone'};
        }
        if ($flag->requestee && $requestees{$flag->requestee->login}) {
          delete $requestees{$flag->requestee->login};
        }
      }

      foreach my $requestee (keys %requestees) {
        my $needinfo_flag = {type_id => $type->id, status => '?'};
        if ($requestee ne 'anyone') {
          _check_requestee($requestee);
          $needinfo_flag->{requestee} = $requestee;
          my $requestee_obj = Bugzilla::User->check($requestee);
          if (!$requestee_obj->can_see_bug($bug->id)) {
            $bug->add_cc($requestee_obj);
          }
        }
        push(@new_flags, $needinfo_flag);
      }
    }
  }

  my @flags;
  foreach my $flag (@{$bug->flags}) {
    next if $flag->type->name ne 'needinfo';

    # Clear if somehow the flag has been set to +/-
    # or if the "clear needinfo" override checkbox is selected
    if ($flag->status ne '?' or grep { $_ == $flag->id } @needinfo_overrides) {
      push(@flags, {id => $flag->id, status => 'X'});
    }
  }

  if ($is_redirect && scalar(@new_flags) == 1) {

    # Find the current user's needinfo request
    foreach my $flag (@{$bug->flags}) {
      next
        unless $flag->type->name eq 'needinfo'
        && $flag->requestee
        && $flag->requestee->id == $user->id;

      # Setting the id on new_flag updates the existing flag instead of
      # creating a new one.
      $new_flags[0]->{id} = $flag->id;
      last;
    }
  }

  if (@flags || @new_flags) {
    $bug->set_flags(\@flags, \@new_flags);
  }
}

sub _check_requestee {
  my ($requestee) = @_;
  my $user
    = ref($requestee)
    ? $requestee
    : Bugzilla::User->new({name => $requestee, cache => 1});
  if ($user->needinfo_blocked) {
    ThrowUserError('needinfo_blocked', {requestee => $user});
  }
}

sub object_end_of_create {
  my ($self, $args) = @_;
  my $object = $args->{object};
  return
       unless $object->isa('Bugzilla::Flag')
    && $object->type->name eq 'needinfo'
    && $object->requestee;
  _check_requestee($object->requestee);
}

sub object_end_of_update {
  my ($self, $args) = @_;
  my $object = $args->{object};
  return
       unless exists $args->{changes}->{requestee_id}
    && $object->isa('Bugzilla::Flag')
    && $object->type->name eq 'needinfo'
    && $object->requestee;
  _check_requestee($object->requestee);
}

sub object_before_delete {
  my ($self, $args) = @_;
  my $object = $args->{object};
  return
    unless $object->isa('Bugzilla::Flag') && $object->type->name eq 'needinfo';
  my $user = Bugzilla->user;

  # Require canconfirm to clear requests targeted at someone else
  if ( $object->setter_id != $user->id
    && $object->requestee
    && $object->requestee->id != $user->id
    && !$user->in_group('canconfirm'))
  {
    ThrowUserError('needinfo_illegal_change');
  }
}

sub user_preferences {
  my ($self, $args) = @_;
  return unless $args->{current_tab} eq 'account' && $args->{save_changes};

  my $input    = Bugzilla->input_params;
  my $settings = Bugzilla->user->settings;

  my $value = $input->{block_needinfo} ? 'on' : 'off';
  $settings->{block_needinfo}->validate_value($value);
  $settings->{block_needinfo}->set($value);
  clear_settings_cache(Bugzilla->user->id);
}

__PACKAGE__->NAME;
