# 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::PhabBugz::Project;

use 5.10.1;
use Moo;
use Scalar::Util qw(blessed);
use Types::Standard -all;
use Type::Utils;
use Type::Params qw( compile );

use Bugzilla::Error;
use Bugzilla::Util qw(trim);
use Bugzilla::Extension::PhabBugz::User;
use Bugzilla::Extension::PhabBugz::Types qw(:types);
use Bugzilla::Extension::PhabBugz::Util qw(request);

#########################
#    Initialization     #
#########################

has id              => (is => 'ro',   isa => Int);
has phid            => (is => 'ro',   isa => Str);
has type            => (is => 'ro',   isa => Str);
has name            => (is => 'ro',   isa => Str);
has description     => (is => 'ro',   isa => Maybe [Str]);
has creation_ts     => (is => 'ro',   isa => Str);
has modification_ts => (is => 'ro',   isa => Str);
has view_policy     => (is => 'ro',   isa => Str);
has edit_policy     => (is => 'ro',   isa => Str);
has join_policy     => (is => 'ro',   isa => Str);
has members_raw     => (is => 'ro',   isa => ArrayRef [Dict [phid => Str]]);
has members         => (is => 'lazy', isa => ArrayRef [PhabUser]);

my $Invocant = class_type {class => __PACKAGE__};

sub new_from_query {
  my ($class, $params) = @_;

  my $data
    = {queryKey => 'all', attachments => {members => 1}, constraints => $params};

  my $result = request('project.search', $data);
  if (exists $result->{result}{data} && @{$result->{result}{data}}) {

    # If name is used as a query param, we need to loop through and look
    # for exact match as Conduit will tokenize the name instead of doing
    # exact string match :( If name is not used, then return first one.
    if (exists $params->{name}) {
      foreach my $item (@{$result->{result}{data}}) {
        next if $item->{fields}{name} ne $params->{name};
        return $class->new($item);
      }
    }
    else {
      return $class->new($result->{result}{data}[0]);
    }
  }
}

sub BUILDARGS {
  my ($class, $params) = @_;

  $params->{name}            = $params->{fields}->{name};
  $params->{description}     = $params->{fields}->{description};
  $params->{creation_ts}     = $params->{fields}->{dateCreated};
  $params->{modification_ts} = $params->{fields}->{dateModified};
  $params->{view_policy}     = $params->{fields}->{policy}->{view};
  $params->{edit_policy}     = $params->{fields}->{policy}->{edit};
  $params->{join_policy}     = $params->{fields}->{policy}->{join};
  $params->{members_raw}     = $params->{attachments}->{members}->{members};

  delete $params->{fields};
  delete $params->{attachments};

  return $params;
}

# {
#   "data": [
#     {
#       "id": 1,
#       "type": "PROJ",
#       "phid": "PHID-PROJ-pfssn7lndryddv7hbx4i",
#       "fields": {
#         "name": "bmo-core-security",
#         "slug": "bmo-core-security",
#         "milestone": null,
#         "depth": 0,
#         "parent": null,
#         "icon": {
#           "key": "group",
#           "name": "Group",
#           "icon": "fa-users"
#         },
#         "color": {
#           "key": "red",
#           "name": "Red"
#         },
#         "dateCreated": 1500403964,
#         "dateModified": 1505248862,
#         "policy": {
#           "view": "secure-revision",
#           "edit": "secure-revision",
#           "join": "secure-revision"
#         },
#         "description": "BMO Security Group for core-security"
#       },
#       "attachments": {
#         "members": {
#           "members": [
#             {
#               "phid": "PHID-USER-23ia7vewbjgcqahewncu"
#             },
#             {
#               "phid": "PHID-USER-uif2miph2poiehjeqn5q"
#             }
#           ]
#         }
#       }
#     }
#   ],
#   "maps": {
#     "slugMap": {}
#   },
#   "query": {
#     "queryKey": null
#   },
#   "cursor": {
#     "limit": 100,
#     "after": null,
#     "before": null,
#     "order": null
#   }
# }

#########################
#     Modification      #
#########################

sub create {
  state $check = compile(
    $Invocant | ClassName,
    Dict [
      name        => Str,
      description => Str,
      view_policy => Str,
      edit_policy => Str,
      join_policy => Str,
    ]
  );
  my ($class, $params) = $check->(@_);

  my $name        = trim($params->{name});
  my $description = $params->{description};
  my $view_policy = $params->{view_policy};
  my $edit_policy = $params->{edit_policy};
  my $join_policy = $params->{join_policy};

  my $data = {
    transactions => [
      {type => 'name',        value => $name},
      {type => 'description', value => $description},
      {type => 'edit',        value => $edit_policy},
      {type => 'join',        value => $join_policy},
      {type => 'view',        value => $view_policy},
      {type => 'icon',        value => 'group'},
      {type => 'color',       value => 'red'}
    ]
  };

  my $result = request('project.edit', $data);

  return $class->new_from_query({phids => [$result->{result}{object}{phid}]});
}

sub update {
  my ($self) = @_;

  my $data = {objectIdentifier => $self->phid, transactions => []};

  if ($self->{set_name}) {
    push(@{$data->{transactions}}, {type => 'name', value => $self->{set_name}});
  }

  if ($self->{set_description}) {
    push(
      @{$data->{transactions}},
      {type => 'description', value => $self->{set_description}}
    );
  }

  if ($self->{set_members}) {
    push(
      @{$data->{transactions}},
      {type => 'members.set', value => $self->{set_members}}
    );
  }
  else {
    if ($self->{add_members}) {
      push(
        @{$data->{transactions}},
        {type => 'members.add', value => $self->{add_members}}
      );
    }

    if ($self->{remove_members}) {
      push(
        @{$data->{transactions}},
        {type => 'members.remove', value => $self->{remove_members}}
      );
    }
  }

  if ($self->{set_policy}) {
    foreach my $name ("view", "edit") {
      next unless $self->{set_policy}->{$name};
      push(
        @{$data->{transactions}},
        {type => $name, value => $self->{set_policy}->{$name}}
      );
    }
  }

  if ($self->{add_projects}) {
    push(
      @{$data->{transactions}},
      {type => 'projects.add', value => $self->{add_projects}}
    );
  }

  if ($self->{remove_projects}) {
    push(
      @{$data->{transactions}},
      {type => 'projects.remove', value => $self->{remove_projects}}
    );
  }

  my $result = request('project.edit', $data);

  return $result;
}

#########################
#       Mutators        #
#########################

sub set_name {
  my ($self, $name) = @_;
  $name = trim($name);
  $self->{set_name} = $name;
}

sub set_description {
  my ($self, $description) = @_;
  $description = trim($description);
  $self->{set_description} = $description;
}

sub add_member {
  my ($self, $member) = @_;
  $self->{add_members} ||= [];
  my $member_phid = blessed $member ? $member->phid : $member;
  push(@{$self->{add_members}}, $member_phid);
}

sub remove_member {
  my ($self, $member) = @_;
  $self->{remove_members} ||= [];
  my $member_phid = blessed $member ? $member->phid : $member;
  push(@{$self->{remove_members}}, $member_phid);
}

sub set_members {
  my ($self, $members) = @_;
  $self->{set_members} = [map { blessed $_ ? $_->phid : $_ } @$members];
}

sub set_policy {
  my ($self, $name, $policy) = @_;
  $self->{set_policy} ||= {};
  $self->{set_policy}->{$name} = $policy;
}

############
# Builders #
############

sub _build_members {
  my ($self) = @_;
  return [] unless $self->members_raw;

  my @phids;
  foreach my $member (@{$self->members_raw}) {
    push(@phids, $member->{phid});
  }

  return [] if !@phids;

  return Bugzilla::Extension::PhabBugz::User->match({phids => \@phids});
}

1;
