# 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::BzAPI::Resources::Bug;

use 5.10.1;
use strict;
use warnings;

use Bugzilla::Bug;
use Bugzilla::Error;
use Bugzilla::Token qw(issue_hash_token);
use Bugzilla::Util qw(diff_arrays);
use Bugzilla::WebService::Constants;

use Bugzilla::Extension::BzAPI::Util;
use Bugzilla::Extension::BzAPI::Constants;

use List::MoreUtils qw(uniq);
use List::Util qw(max);

#################
# REST Handlers #
#################

BEGIN {
  require Bugzilla::WebService::Bug;
  *Bugzilla::WebService::Bug::get_bug_count = \&get_bug_count_resource;
}

sub rest_handlers {
  my $rest_handlers = [
    qr{^/bug$},
    {
      GET  => {request => \&search_bugs_request, response => \&search_bugs_response},
      POST => {request => \&create_bug_request,  response => \&create_bug_response}
    },
    qr{^/bug/([^/]+)$},
    {
      GET => {response => \&get_bug_response},
      PUT => {request  => \&update_bug_request, response => \&update_bug_response}
    },
    qr{^/bug/([^/]+)/comment$},
    {
      GET  => {response => \&get_comments_response},
      POST => {request  => \&add_comment_request, response => \&add_comment_response}
    },
    qr{^/bug/([^/]+)/history$},
    {GET => {response => \&get_history_response}},
    qr{^/bug/([^/]+)/attachment$},
    {
      GET => {response => \&get_attachments_response},
      POST =>
        {request => \&add_attachment_request, response => \&add_attachment_response}
    },
    qr{^/bug/attachment/([^/]+)$},
    {
      GET => {response => \&get_attachment_response},
      PUT => {
        request  => \&update_attachment_request,
        response => \&update_attachment_response
      }
    },
    qr{^/attachment/([^/]+)$},
    {
      GET => {response => \&get_attachment_response},
      PUT => {
        request  => \&update_attachment_request,
        response => \&update_attachment_response
      }
    },
    qr{^/bug/([^/]+)/flag$},
    {
      GET => {
        resource => {
          method => 'get',
          params => sub {
            return {ids => [$_[0]], include_fields => ['flags']};
          }
        },
        response => \&get_bug_flags_response,
      }
    },
    qr{^/count$},
    {GET => {resource => {method => 'get_bug_count'}}},
    qr{^/attachment/([^/]+)$},
    {
      GET => {
        resource => {
          method => 'attachments',
          params => sub {
            return {attachment_ids => [$_[0]]};
          }
        }
      },
      PUT => {
        resource => {
          method => 'update_attachment',
          params => sub {
            return {ids => [$_[0]]};
          }
        }
      }
    }
  ];
  return $rest_handlers;
}

#########################
# REST Resource Methods #
#########################

# Return bug counts based on row/col/table fields
# FIXME Borrowed a lot of code from report.cgi, eventually
# this should be broken into it's own module so that report.cgi
# and here can share the same code.
sub get_bug_count_resource {
  my ($self, $params) = @_;

  Bugzilla->switch_to_shadow_db();

  my $col_field = $params->{x_axis_field} || '';
  my $row_field = $params->{y_axis_field} || '';
  my $tbl_field = $params->{z_axis_field} || '';

  my $dimensions = $col_field ? $row_field ? $tbl_field ? 3 : 2 : 1 : 0;

  if ($dimensions == 0) {
    $col_field = "bug_status";
    $params->{x_axis_field} = "bug_status";
  }

  # Valid bug fields that can be reported on.
  my $valid_columns = Bugzilla::Search::REPORT_COLUMNS;

  # Convert external names to internal if necessary
  $params    = Bugzilla::Bug::map_fields($params);
  $row_field = Bugzilla::Bug::FIELD_MAP->{$row_field} || $row_field;
  $col_field = Bugzilla::Bug::FIELD_MAP->{$col_field} || $col_field;
  $tbl_field = Bugzilla::Bug::FIELD_MAP->{$tbl_field} || $tbl_field;

  # Validate the values in the axis fields or throw an error.
  !$row_field
    || $valid_columns->{$row_field}
    || ThrowCodeError("report_axis_invalid", {fld => "x", val => $row_field});
  !$col_field
    || $valid_columns->{$col_field}
    || ThrowCodeError("report_axis_invalid", {fld => "y", val => $col_field});
  !$tbl_field
    || $valid_columns->{$tbl_field}
    || ThrowCodeError("report_axis_invalid", {fld => "z", val => $tbl_field});

  my @axis_fields = grep {$_} ($row_field, $col_field, $tbl_field);

  my $search = new Bugzilla::Search(
    fields          => \@axis_fields,
    params          => $params,
    allow_unlimited => 1,
  );

  my ($results, $extra_data) = $search->data;

  # We have a hash of hashes for the data itself, and a hash to hold the
  # row/col/table names.
  my %data;
  my %names;

  # Read the bug data and count the bugs for each possible value of row, column
  # and table.
  #
  # We detect a numerical field, and sort appropriately, if all the values are
  # numeric.
  my $col_isnumeric = 1;
  my $row_isnumeric = 1;
  my $tbl_isnumeric = 1;

  foreach my $result (@$results) {

    # handle empty dimension member names
    my $row = check_value($row_field, $result);
    my $col = check_value($col_field, $result);
    my $tbl = check_value($tbl_field, $result);

    $data{$tbl}{$col}{$row}++;
    $names{"col"}{$col}++;
    $names{"row"}{$row}++;
    $names{"tbl"}{$tbl}++;

    $col_isnumeric &&= ($col =~ /^-?\d+(\.\d+)?$/o);
    $row_isnumeric &&= ($row =~ /^-?\d+(\.\d+)?$/o);
    $tbl_isnumeric &&= ($tbl =~ /^-?\d+(\.\d+)?$/o);
  }

  my @col_names = get_names($names{"col"}, $col_isnumeric, $col_field);
  my @row_names = get_names($names{"row"}, $row_isnumeric, $row_field);
  my @tbl_names = get_names($names{"tbl"}, $tbl_isnumeric, $tbl_field);

  push(@tbl_names, "-total-") if (scalar(@tbl_names) > 1);

  my @data;
  foreach my $tbl (@tbl_names) {
    my @tbl_data;
    foreach my $row (@row_names) {
      my @col_data;
      foreach my $col (@col_names) {
        $data{$tbl}{$col}{$row} = $data{$tbl}{$col}{$row} || 0;
        push(@col_data, $data{$tbl}{$col}{$row});
        if ($tbl ne "-total-") {

          # This is a bit sneaky. We spend every loop except the last
          # building up the -total- data, and then last time round,
          # we process it as another tbl, and push() the total values
          # into the image_data array.
          $data{"-total-"}{$col}{$row} += $data{$tbl}{$col}{$row};
        }
      }
      push(@tbl_data, \@col_data);
    }
    push(@data, \@tbl_data);
  }

  my $result = {};
  if ($dimensions == 0) {
    my $sum = 0;

    # If the search returns no results, we just get an 0-byte file back
    # and so there is no data at all.
    if (@data) {
      foreach my $value (@{$data[0][0]}) {
        $sum += $value;
      }
    }

    $result = {'data' => $sum};
  }
  elsif ($dimensions == 1) {
    $result = {'x_labels' => \@col_names, 'data' => $data[0][0] || []};
  }
  elsif ($dimensions == 2) {
    $result = {
      'x_labels' => \@col_names,
      'y_labels' => \@row_names,
      'data'     => $data[0] || [[]]
    };
  }
  elsif ($dimensions == 3) {
    if (@data > 1 && $tbl_names[-1] eq "-total-") {

      # Last table is a total, which we discard
      pop(@data);
      pop(@tbl_names);
    }

    $result = {
      'x_labels' => \@col_names,
      'y_labels' => \@row_names,
      'z_labels' => \@tbl_names,
      'data'     => @data ? \@data : [[[]]]
    };
  }

  return $result;
}

sub get_names {
  my ($names, $isnumeric, $field_name) = @_;
  my ($field, @sorted);

  # XXX - This is a hack to handle the actual_time/work_time field,
  # because it's named 'actual_time' in Search.pm but 'work_time' in Field.pm.
  $_[2] = $field_name = 'work_time' if $field_name eq 'actual_time';

  # _realname fields aren't real Bugzilla::Field objects, but they are a
  # valid axis, so we don't vailidate them as Bugzilla::Field objects.
  $field = Bugzilla::Field->check($field_name)
    if ($field_name && $field_name !~ /_realname$/);

  if ($field && $field->is_select) {
    foreach my $value (@{$field->legal_values}) {
      push(@sorted, $value->name) if $names->{$value->name};
    }
    unshift(@sorted, '---') if $field_name eq 'resolution';
    @sorted = uniq @sorted;
  }
  elsif ($isnumeric) {

    # It's not a field we are preserving the order of, so sort it
    # numerically...
    @sorted = sort { $a <=> $b } keys %$names;
  }
  else {
    # ...or alphabetically, as appropriate.
    @sorted = sort keys %$names;
  }

  return @sorted;
}

sub check_value {
  my ($field, $result) = @_;

  my $value;
  if (!defined $field) {
    $value = '';
  }
  elsif ($field eq '') {
    $value = ' ';
  }
  else {
    $value = shift @$result;
    $value = ' ' if (!defined $value || $value eq '');
    $value = '---' if ($field eq 'resolution' && $value eq ' ');
  }
  return $value;
}

########################
# REST Request Methods #
########################

sub search_bugs_request {
  my ($params) = @_;

  if (defined $params->{changed_field}
    && $params->{changed_field} eq "creation_time")
  {
    $params->{changed_field} = "[Bug creation]";
  }

  my $FIELD_NEW_TO_OLD = {reverse %{BUG_FIELD_MAP()}};

  # Update values of various forms.
  foreach my $key (keys %$params) {

    # First, search types. These are found in the value of any field ending
    # _type, and the value of any field matching type\d-\d-\d.
    if ($key =~ /^type(\d+)-(\d+)-(\d+)$|_type$/) {
      $params->{$key} = BOOLEAN_TYPE_MAP->{$params->{$key}} || $params->{$key};
    }

    # Field names hiding in values instead of keys: changed_field, boolean
    # charts and axis names.
    if (
      $key =~ /^(field\d+-\d+-\d+|
                    changed_field|
                    (x|y|z)_axis_field)$
                    /x
      )
    {
      $params->{$key} = $FIELD_NEW_TO_OLD->{$params->{$key}} || $params->{$key};
    }
  }

  # Update field names
  foreach my $field (keys %$FIELD_NEW_TO_OLD) {
    if (defined $params->{$field}) {
      $params->{$FIELD_NEW_TO_OLD->{$field}} = delete $params->{$field};
    }
  }

  if (exists $params->{bug_id_type}) {
    $params->{bug_id_type}
      = BOOLEAN_TYPE_MAP->{$params->{bug_id_type}} || $params->{bug_id_type};
  }

  # Time field names are screwy, and got reused. We can't put this mapping
  # in NEW2OLD as everything will go haywire. actual_time has to be queried
  # as work_time even though work_time is the submit-only field for _adding_
  # to actual_time, which can't be arbitrarily manipulated.
  if (defined $params->{work_time}) {
    $params->{actual_time} = delete $params->{work_time};
  }

  # Other convenience search variables used by BzAPI
  my @field_ids = grep(/^f(\d+)$/, keys %$params);
  my $last_field_id = @field_ids ? max @field_ids + 1 : 1;
  foreach my $field (qw(setters.login_name requestees.login_name)) {
    if (my $value = delete $params->{$field}) {
      $params->{"f${last_field_id}"} = $FIELD_NEW_TO_OLD->{$field} || $field;
      $params->{"o${last_field_id}"} = 'equals';
      $params->{"v${last_field_id}"} = $value;
      $last_field_id++;
    }
  }
}

sub create_bug_request {
  my ($params) = @_;

  # User roles such as assigned_to and qa_contact should be just the
  # email (login) of the user you want to set to.
  foreach my $field (qw(assigned_to qa_contact)) {
    if (exists $params->{$field}) {
      $params->{$field} = $params->{$field}->{name};
    }
  }

  # CC should just be a list of Bugzilla logins
  if (exists $params->{cc}) {
    $params->{cc} = [map { $_->{name} } @{$params->{cc}}];
  }

  # Comment
  if (exists $params->{comments}) {
    $params->{comment_is_private} = $params->{comments}->[0]->{is_private};
    $params->{description}        = $params->{comments}->[0]->{text};
    delete $params->{comments};
  }

  # Some fields are not supported by Bugzilla::Bug->create but are supported
  # by Bugzilla::Bug->update :(
  my $cache = Bugzilla->request_cache->{bzapi_bug_create_extra} ||= {};
  foreach my $field (qw(remaining_time)) {
    next if !exists $params->{$field};
    $cache->{$field} = delete $params->{$field};
  }

  # remove username/password
  delete $params->{username};
  delete $params->{password};
}

sub update_bug_request {
  my ($params) = @_;

  my $bug_id = ref $params->{ids} ? $params->{ids}->[0] : $params->{ids};
  my $bug = Bugzilla::Bug->check($bug_id);

  # Convert groups to proper add/remove lists
  if (exists $params->{groups}) {
    my @new_groups = map { $_->{name} } @{$params->{groups}};
    my @old_groups = map { $_->name } @{$bug->groups_in};
    my ($removed, $added) = diff_arrays(\@old_groups, \@new_groups);
    if (@$added || @$removed) {
      my $groups_data = {};
      $groups_data->{add}    = $added   if @$added;
      $groups_data->{remove} = $removed if @$removed;
      $params->{groups}      = $groups_data;
    }
    else {
      delete $params->{groups};
    }
  }

  # Other fields such as dependencies, regressions and keywords
  # support 'set' which will make the list exactly what
  # the user passes in.
  foreach my $field (qw(blocks depends_on dependson regresses regressed_by keywords)) {
    if (exists $params->{$field}) {
      $params->{$field} = {set => $params->{$field}};
    }
  }

  # User roles such as assigned_to and qa_contact should be just the
  # email (login) of the user you want to change to. Also if defined
  # but set to NULL then we reset them to default
  foreach my $field (qw(assigned_to qa_contact)) {
    if (exists $params->{$field}) {
      if (!$params->{$field}) {
        $params->{"reset_$field"} = 1;
        delete $params->{$field};
      }
      else {
        $params->{$field} = $params->{$field}->{name};
      }
    }
  }

  # CC is treated like groups in that we need 'add' and 'remove' keys
  if (exists $params->{cc}) {
    my $new_cc = [map { $_->{name} } @{$params->{cc}}];
    my ($removed, $added) = diff_arrays($bug->cc, $new_cc);
    if (@$added || @$removed) {
      my $cc_data = {};
      $cc_data->{add}    = $added   if @$added;
      $cc_data->{remove} = $removed if @$removed;
      $params->{cc}      = $cc_data;
    }
    else {
      delete $params->{cc};
    }
  }

  # see_also is treated like groups in that we need 'add' and 'remove' keys
  if (exists $params->{see_also}) {
    my $old_see_also = [map { $_->name } @{$bug->see_also}];
    my ($removed, $added) = diff_arrays($old_see_also, $params->{see_also});
    if (@$added || @$removed) {
      my $data = {};
      $data->{add}    = $added   if @$added;
      $data->{remove} = $removed if @$removed;
      $params->{see_also} = $data;
    }
    else {
      delete $params->{see_also};
    }
  }

  # BzAPI allows for adding comments by appending to the list of current
  # comments and passing the whole list back.
  # 1. If a comment id is specified, the user can update the comment privacy
  # 2. If no id is specified it is considered a new comment but only the last
  #    one will be accepted.
  my %comment_is_private;
  foreach my $comment (@{$params->{'comments'}}) {
    if (my $id = $comment->{'id'}) {

      # Existing comment; tweak privacy flags if necessary
      $comment_is_private{$id}
        = ($comment->{'is_private'} && $comment->{'is_private'} eq "true") ? 1 : 0;
    }
    else {
      # New comment to be added
      # If multiple new comments are specified, only the last one will be
      # added.
      $params->{comment} = {
        body       => $comment->{text},
        is_private => ($comment->{'is_private'} && $comment->{'is_private'} eq "true")
        ? 1
        : 0
      };
    }
  }
  $params->{comment_is_private} = \%comment_is_private if %comment_is_private;

  # Remove setter and convert requestee to just name
  if (exists $params->{flags}) {
    foreach my $flag (@{$params->{flags}}) {
      delete $flag->{setter};    # Always use logged in user
      if (exists $flag->{requestee} && ref $flag->{requestee}) {
        $flag->{requestee} = $flag->{requestee}->{name};
      }

      # If no flag id provided, assume it is new
      if (!exists $flag->{id}) {
        $flag->{new} = 1;
      }
    }
  }
}

sub add_comment_request {
  my ($params) = @_;
  $params->{comment} = delete $params->{text} if $params->{text};
}

sub add_attachment_request {
  my ($params) = @_;

  # Bug.add_attachment uses 'summary' for description.
  if ($params->{description}) {
    $params->{summary} = $params->{description};
    delete $params->{description};
  }

  # Remove setter and convert requestee to just name
  if (exists $params->{flags}) {
    foreach my $flag (@{$params->{flags}}) {
      delete $flag->{setter};    # Always use logged in user
      if (exists $flag->{requestee} && ref $flag->{requestee}) {
        $flag->{requestee} = $flag->{requestee}->{name};
      }
    }
  }

  # Add comment if one is provided
  if (exists $params->{comments} && scalar @{$params->{comments}}) {
    $params->{comment} = $params->{comments}->[0]->{text};
    delete $params->{comments};
  }
}

sub update_attachment_request {
  my ($params) = @_;

  # Stash away for midair checking later
  if ($params->{last_change_time}) {
    my $stash = Bugzilla->request_cache->{bzapi_stash} ||= {};
    $stash->{last_change_time} = delete $params->{last_change_time};
  }

  # Immutable values
  foreach my $key (
    qw(attacher bug_id bug_ref creation_time
    encoding id ref size update_token)
    )
  {
    delete $params->{$key};
  }

  # Convert setter and requestee to standard values
  if (exists $params->{flags}) {
    foreach my $flag (@{$params->{flags}}) {
      delete $flag->{setter};    # Always use logged in user
      if (exists $flag->{requestee} && ref $flag->{requestee}) {
        $flag->{requestee} = $flag->{requestee}->{name};
      }
    }
  }

  # Add comment if one is provided
  if (exists $params->{comments} && scalar @{$params->{comments}}) {
    $params->{comment} = $params->{comments}->[0]->{text};
    delete $params->{comments};
  }
}

#########################
# REST Response Methods #
#########################

sub search_bugs_response {
  my ($result, $response) = @_;
  my $cache  = Bugzilla->request_cache;
  my $params = Bugzilla->input_params;

  return if !exists $$result->{bugs};

  my $bug_objs = $cache->{bzapi_search_bugs};

  my @fixed_bugs;
  my $stash = {};
  foreach my $bug_data (@{$$result->{bugs}}) {
    my $bug_obj = shift @$bug_objs;
    my $fixed = fix_bug($bug_data, $bug_obj, $stash);

    # CC count and Dupe count
    if (filter_wants_nocache($params, 'cc_count')) {
      $fixed->{cc_count} = scalar @{$bug_obj->cc} if $bug_obj->cc;
    }
    if (filter_wants_nocache($params, 'dupe_count')) {
      $fixed->{dupe_count} = scalar @{$bug_obj->duplicate_ids}
        if $bug_obj->duplicate_ids;
    }

    push(@fixed_bugs, $fixed);
  }

  $$result->{bugs} = \@fixed_bugs;
}

sub create_bug_response {
  my ($result, $response) = @_;
  my $rpc = Bugzilla->request_cache->{bzapi_rpc};

  return if !exists $$result->{id};
  my $bug_id = $$result->{id};

  $$result->{ref} = $rpc->type('string', ref_urlbase() . "/bug/$bug_id");
  $response->code(STATUS_CREATED);
}

sub get_bug_response {
  my ($result) = @_;
  my $rpc = Bugzilla->request_cache->{bzapi_rpc};

  return if !exists $$result->{bugs};
  my $bug_data = $$result->{bugs}->[0];

  my $bug_id  = $rpc->bz_rest_params->{ids}->[0];
  my $bug_obj = Bugzilla::Bug->check($bug_id);
  my $fixed   = fix_bug($bug_data, $bug_obj);

  $$result = $fixed;
}

sub update_bug_response {
  my ($result) = @_;
  return if !exists $$result->{bugs} || !scalar @{$$result->{bugs}};
  $$result = {ok => 1};
}

# Get all comments for a bug
sub get_comments_response {
  my ($result) = @_;
  my $rpc      = Bugzilla->request_cache->{bzapi_rpc};
  my $params   = Bugzilla->input_params;

  return if !exists $$result->{bugs};

  my $bug_id = $rpc->bz_rest_params->{ids}->[0];
  my $bug    = Bugzilla::Bug->check($bug_id);

  my $comment_objs
    = $bug->comments({order => 'oldest_to_newest', after => $params->{new_since}
    });
  my @filtered_comment_objs;
  foreach my $comment (@$comment_objs) {
    next if $comment->is_private && !Bugzilla->user->is_insider;
    push(@filtered_comment_objs, $comment);
  }

  my $comments_data = $$result->{bugs}->{$bug_id}->{comments};

  my @fixed_comments;
  foreach my $comment_data (@$comments_data) {
    my $comment_obj = shift @filtered_comment_objs;
    my $fixed = fix_comment($comment_data, $comment_obj);

    if (exists $fixed->{creator}) {

# /bug/<ID>/comment returns full login for creator but not for /bug/<ID>?include_fields=comments :(
      $fixed->{creator}->{name} = $rpc->type('string', $comment_obj->author->login);

      # /bug/<ID>/comment does not return real_name for creator but returns ref
      $fixed->{creator}->{'ref'} = $rpc->type('string',
        ref_urlbase() . "/user/" . $comment_obj->author->login);
      delete $fixed->{creator}->{real_name};
    }

    push(@fixed_comments, filter($params, $fixed));
  }

  $$result = {comments => \@fixed_comments};
}

# Format the return response on successful comment creation
sub add_comment_response {
  my ($result, $response) = @_;
  my $rpc = Bugzilla->request_cache->{bzapi_rpc};

  return if !exists $$result->{id};
  my $bug_id = $rpc->bz_rest_params->{id};

  $$result
    = {ref => $rpc->type('string', ref_urlbase() . "/bug/$bug_id/comment")};
  $response->code(STATUS_CREATED);
}

# Get the history for a bug
sub get_history_response {
  my ($result) = @_;
  my $params = Bugzilla->input_params;

  return if !exists $$result->{bugs};
  my $history = $$result->{bugs}->[0]->{history};

  my @new_history;
  foreach my $changeset (@$history) {
    $changeset = fix_changeset($changeset);
    push(@new_history, filter($params, $changeset));
  }

  $$result = {history => \@new_history};
}

# Get all attachments for a bug
sub get_attachments_response {
  my ($result) = @_;
  my $rpc      = Bugzilla->request_cache->{bzapi_rpc};
  my $params   = Bugzilla->input_params;

  return if !exists $$result->{bugs};
  my $bug_id          = $rpc->bz_rest_params->{ids}->[0];
  my $bug             = Bugzilla::Bug->check($bug_id);
  my $attachment_objs = $bug->attachments;

  my $attachments_data = $$result->{bugs}->{$bug_id};

  my @fixed_attachments;
  foreach my $attachment (@$attachments_data) {
    my $attachment_obj = shift @$attachment_objs;
    my $fixed = fix_attachment($attachment, $attachment_obj);

    if ((
         filter_wants_nocache($params, 'data', 'extra')
      || filter_wants_nocache($params, 'encoding', 'extra')
      || $params->{attachmentdata}
    ))
    {
      if (!$fixed->{data}) {
        $fixed->{data}     = $rpc->type('base64', $attachment_obj->data);
        $fixed->{encoding} = $rpc->type('string', 'base64');
      }
    }
    else {
      delete $fixed->{data};
      delete $fixed->{encoding};
    }

    push(@fixed_attachments, filter($params, $fixed));
  }

  $$result = {attachments => \@fixed_attachments};
}

# Format the return response on successful attachment creation
sub add_attachment_response {
  my ($result, $response) = @_;
  my $rpc = Bugzilla->request_cache->{bzapi_rpc};

  my ($attach_id) = keys %{$$result->{attachments}};

  $$result = {
    ref => $rpc->type('string', ref_urlbase() . "/attachment/$attach_id"),
    id  => $attach_id
  };
  $response->code(STATUS_CREATED);
}

# Update an attachment's metadata
sub update_attachment_response {
  my ($result) = @_;
  $$result = {ok => 1};
}

# Get a single attachment by attachment_id
sub get_attachment_response {
  my ($result) = @_;
  my $rpc      = Bugzilla->request_cache->{bzapi_rpc};
  my $params   = Bugzilla->input_params;

  return if !exists $$result->{attachments};
  my $attach_id       = $rpc->bz_rest_params->{attachment_ids}->[0];
  my $attachment_data = $$result->{attachments}->{$attach_id};
  my $attachment_obj  = Bugzilla::Attachment->new($attach_id);
  my $fixed           = fix_attachment($attachment_data, $attachment_obj);

  if ((
       filter_wants_nocache($params, 'data', 'extra')
    || filter_wants_nocache($params, 'encoding', 'extra')
    || $params->{attachmentdata}
  ))
  {
    if (!$fixed->{data}) {
      $fixed->{data}     = $rpc->type('base64', $attachment_obj->data);
      $fixed->{encoding} = $rpc->type('string', 'base64');
    }
  }
  else {
    delete $fixed->{data};
    delete $fixed->{encoding};
  }

  $fixed = filter($params, $fixed);

  $$result = $fixed;
}

# Get a list of flags for a bug
sub get_bug_flags_response {
  my ($result) = @_;
  my $params = Bugzilla->input_params;

  return if !exists $$result->{bugs};
  my $flags = $$result->{bugs}->[0]->{flags};

  my @new_flags;
  foreach my $flag (@$flags) {
    push(@new_flags, fix_flag($flag));
  }

  $$result = {flags => \@new_flags};
}

1;
