# 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::BMO::Reports::UserActivity;

use 5.10.1;
use strict;
use warnings;

use Bugzilla::Error;
use Bugzilla::User;
use Bugzilla::Util qw(trim);
use Bugzilla::Util::DateTime;
use DateTime;

sub report {
  my ($vars) = @_;
  my $dbh    = Bugzilla->dbh;
  my $input  = Bugzilla->input_params;

  my @who    = ();
  my $from   = trim($input->{'from'} || '');
  my $to     = trim($input->{'to'} || '');
  my $action = $input->{'action'} || '';

  # fix non-breaking hyphens
  $from =~ s/\N{U+2011}/-/g;
  $to =~ s/\N{U+2011}/-/g;

  if ($from eq '') {
    my $dt = DateTime->now()->subtract('weeks' => 1);
    $from = $dt->ymd('-');
  }
  if ($to eq '') {
    my $dt = DateTime->now();
    $to = $dt->ymd('-');
  }

  if ($action eq 'run') {
    if (!exists $input->{'who'} || $input->{'who'} eq '') {
      ThrowUserError('user_activity_missing_username');
    }
    Bugzilla::User::match_field({'who' => {'type' => 'multi'}});

    my $from_dt = string_to_datetime($from);
    $from = $from_dt->ymd();

    my $to_dt = string_to_datetime($to);
    $to = $to_dt->ymd();

    my ($activity_joins,      $activity_where)      = ('', '');
    my ($attachments_joins,   $attachments_where)   = ('', '');
    my ($tags_activity_joins, $tags_activity_where) = ('', '');
    if (Bugzilla->params->{"insidergroup"}
      && !Bugzilla->user->in_group(Bugzilla->params->{'insidergroup'}))
    {
      $activity_joins = "LEFT JOIN attachments
                       ON attachments.attach_id = bugs_activity.attach_id";
      $activity_where    = "AND COALESCE(attachments.isprivate, 0) = 0";
      $attachments_where = $activity_where;

      $tags_activity_joins = 'LEFT JOIN longdescs
                ON longdescs_tags_activity.comment_id = longdescs.comment_id';
      $tags_activity_where = 'AND COALESCE(longdescs.isprivate, 0) = 0';
    }

    my @who_bits;
    foreach my $who (ref $input->{'who'} ? @{$input->{'who'}} : $input->{'who'}) {
      push @who,      $who;
      push @who_bits, '?';
    }
    my $who_bits = join(',', @who_bits);

    if (!@who) {
      my $template = Bugzilla->template;
      my $cgi      = Bugzilla->cgi;
      my $vars     = {};
      $vars->{'script'}        = $cgi->url(-relative => 1);
      $vars->{'fields'}        = {};
      $vars->{'matches'}       = [];
      $vars->{'matchsuccess'}  = 0;
      $vars->{'matchmultiple'} = 1;
      print $cgi->header();
      $template->process("global/confirm-user-match.html.tmpl", $vars)
        || ThrowTemplateError($template->error());
      exit;
    }

    $from_dt = $from_dt->ymd() . ' 00:00:00';
    $to_dt   = $to_dt->ymd() . ' 23:59:59';
    my @params;
    for (1 .. 5) {
      push @params, @who;
      push @params, ($from_dt, $to_dt);
    }

    my $order
      = ($input->{'group'} && $input->{'group'} eq 'bug')
      ? 'bug_id, bug_when'
      : 'bug_when';

    my $comment_filter = '';
    if (!Bugzilla->user->is_insider) {
      $comment_filter = 'AND longdescs.isprivate = 0';
    }

    my $query = "
        SELECT
                   fielddefs.name,
                   bugs_activity.bug_id,
                   bugs_activity.attach_id,
                   "
      . $dbh->sql_date_format('bugs_activity.bug_when', '%Y-%m-%d %H:%i:%s')
      . " AS ts,
                   bugs_activity.removed,
                   bugs_activity.added,
                   profiles.login_name,
                   bugs_activity.comment_id,
                   bugs_activity.bug_when
              FROM bugs_activity
                   $activity_joins
         LEFT JOIN fielddefs
                ON bugs_activity.fieldid = fielddefs.id
        INNER JOIN profiles
                ON profiles.userid = bugs_activity.who
             WHERE profiles.login_name IN ($who_bits)
                   AND CONVERT_TZ(bugs_activity.bug_when, 'UTC', 'America/Los_Angeles') >= ?
                   AND CONVERT_TZ(bugs_activity.bug_when, 'UTC', 'America/Los_Angeles') <= ?
                   $activity_where

        UNION ALL

        SELECT
                   'comment_tag' AS name,
                   longdescs_tags_activity.bug_id,
                   NULL as attach_id,
                   "
      . $dbh->sql_date_format('longdescs_tags_activity.bug_when',
      '%Y-%m-%d %H:%i:%s')
      . " AS bug_when,
                   longdescs_tags_activity.removed,
                   longdescs_tags_activity.added,
                   profiles.login_name,
                   longdescs_tags_activity.comment_id,
                   longdescs_tags_activity.bug_when
              FROM longdescs_tags_activity
                   $tags_activity_joins
        INNER JOIN profiles
                ON profiles.userid = longdescs_tags_activity.who
             WHERE profiles.login_name IN ($who_bits)
                   AND CONVERT_TZ(longdescs_tags_activity.bug_when, 'UTC', 'America/Los_Angeles') >= ?
                   AND CONVERT_TZ(longdescs_tags_activity.bug_when, 'UTC', 'America/Los_Angeles') <= ?
                  $tags_activity_where

        UNION ALL

        SELECT
                   'bug_id' AS name,
                   bugs.bug_id,
                   NULL AS attach_id,
                   "
      . $dbh->sql_date_format('bugs.creation_ts', '%Y-%m-%d %H:%i:%s') . " AS ts,
                   '(new bug)' AS removed,
                   bugs.short_desc AS added,
                   profiles.login_name,
                   NULL AS comment_id,
                   bugs.creation_ts AS bug_when
              FROM bugs
        INNER JOIN profiles
                ON profiles.userid = bugs.reporter
             WHERE profiles.login_name IN ($who_bits)
                   AND CONVERT_TZ(bugs.creation_ts, 'UTC', 'America/Los_Angeles') >= ?
                   AND CONVERT_TZ(bugs.creation_ts, 'UTC', 'America/Los_Angeles') <= ?

        UNION ALL

        SELECT
                   'longdesc' AS name,
                   longdescs.bug_id,
                   NULL AS attach_id,
                   DATE_FORMAT(longdescs.bug_when, '%Y-%m-%d %H:%i:%s') AS ts,
                   '' AS removed,
                   '' AS added,
                   profiles.login_name,
                   longdescs.comment_id AS comment_id,
                   longdescs.bug_when
              FROM longdescs
        INNER JOIN profiles
                ON profiles.userid = longdescs.who
             WHERE profiles.login_name IN ($who_bits)
                   AND CONVERT_TZ(longdescs.bug_when, 'UTC', 'America/Los_Angeles') >= ?
                   AND CONVERT_TZ(longdescs.bug_when, 'UTC', 'America/Los_Angeles') <= ?
                   $comment_filter

        UNION ALL

        SELECT
                   'attachments.description' AS name,
                   attachments.bug_id,
                   attachments.attach_id,
                   "
      . $dbh->sql_date_format('attachments.creation_ts', '%Y-%m-%d %H:%i:%s')
      . " AS ts,
                   '(new attachment)' AS removed,
                   attachments.description AS added,
                   profiles.login_name,
                   NULL AS comment_id,
                   attachments.creation_ts AS bug_when
              FROM attachments
        INNER JOIN profiles
                ON profiles.userid = attachments.submitter_id
             WHERE profiles.login_name IN ($who_bits)
                   AND CONVERT_TZ(attachments.creation_ts, 'UTC', 'America/Los_Angeles') >= ?
                   AND CONVERT_TZ(attachments.creation_ts, 'UTC', 'America/Los_Angeles') <= ?
                   $attachments_where

          ORDER BY $order ";

    my $list = $dbh->selectall_arrayref($query, undef, @params);

    if ($input->{debug}) {
      while (my $param = shift @params) {
        $query =~ s/\?/$dbh->quote($param)/e;
      }
      $vars->{debug_sql} = $query;
    }

    my @operations;
    my $operation       = {};
    my $changes         = [];
    my $incomplete_data = 0;
    my %bug_ids;

    foreach my $entry (@$list) {
      my ($fieldname, $bugid, $attachid, $when, $removed, $added, $who, $comment_id)
        = @$entry;
      my %change;
      my $activity_visible = 1;

      next unless Bugzilla->user->can_see_bug($bugid);

      # check if the user should see this field's activity
      if ( $fieldname eq 'remaining_time'
        || $fieldname eq 'estimated_time'
        || $fieldname eq 'work_time'
        || $fieldname eq 'deadline')
      {
        $activity_visible = Bugzilla->user->is_timetracker;
      }
      elsif ($fieldname eq 'longdescs.isprivate'
        && !Bugzilla->user->is_insider
        && $added)
      {
        $activity_visible = 0;
      }
      else {
        $activity_visible = 1;
      }

      if ($activity_visible) {

        # Check for the results of an old Bugzilla data corruption bug
        if ( ($added eq '?' && $removed eq '?')
          || ($added =~ /^\? / || $removed =~ /^\? /))
        {
          $incomplete_data = 1;
        }

        # Start a new changeset if required (depends on the grouping type)
        my $is_new_changeset;
        if ($order eq 'bug_when') {
          $is_new_changeset
            = $operation->{'who'}
            && ($who ne $operation->{'who'}
            || $when ne $operation->{'when'}
            || $bugid != $operation->{'bug'});
        }
        else {
          $is_new_changeset = $operation->{'bug'} && $bugid != $operation->{'bug'};
        }
        if ($is_new_changeset) {
          $operation->{'changes'} = $changes;
          push(@operations, $operation);
          $operation = {};
          $changes   = [];
        }

        $bug_ids{$bugid} = 1;

        $operation->{'bug'}  = $bugid;
        $operation->{'who'}  = $who;
        $operation->{'when'} = $when;

        $change{'fieldname'} = $fieldname;
        $change{'attachid'}  = $attachid;
        $change{'removed'}   = $removed;
        $change{'added'}     = $added;
        $change{'when'}      = $when;

        if ($comment_id) {
          $change{'comment'} = Bugzilla::Comment->new($comment_id);
          next if $change{'comment'}->count == 0;
        }

        if ($attachid) {
          $change{'attach'} = Bugzilla::Attachment->new($attachid);
        }

        push(@$changes, \%change);
      }
    }

    if ($operation->{'who'}) {
      $operation->{'changes'} = $changes;
      push(@operations, $operation);
    }

    $vars->{'incomplete_data'} = $incomplete_data;
    $vars->{'operations'}      = \@operations;

    my @bug_ids = sort { $a <=> $b } keys %bug_ids;
    $vars->{'bug_ids'} = \@bug_ids;
  }

  $vars->{'action'}    = $action;
  $vars->{'who'}       = join(',', @who);
  $vars->{'who_count'} = scalar @who;
  $vars->{'from'}      = $from;
  $vars->{'to'}        = $to;
  $vars->{'group'}     = $input->{'group'};
}

1;
