# 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.

use 5.14.0;
use strict;
use warnings;

use FindBin qw($RealBin);
use lib "$RealBin/../lib", "$RealBin/../../local/lib/perl5";

use Data::Dumper;
use Test::More;
use List::Util qw(first);
use QA::Util;

my ($config, @clients) = get_rpc_clients();
plan tests => ($config->{test_extensions} ? 1338 : 1320);

use constant INVALID_FIELD_NAME => 'invalid_field';
use constant INVALID_FIELD_ID => -1;
sub GLOBAL_GENERAL_FIELDS {
    my @fields = qw(
        attach_data.thedata
        attachments.description
        attachments.filename
        attachments.isobsolete
        attachments.ispatch
        attachments.isprivate
        attachments.mimetype
        attachments.submitter

        flagtypes.name
        requestees.login_name
        setters.login_name

        alias
        assigned_to
        blocked
        bug_file_loc
        bug_group
        bug_id
        cc
        cclist_accessible
        classification
        commenter
        content
        creation_ts
        days_elapsed
        delta_ts
        dependson
        everconfirmed
        keywords
        longdesc
        longdescs.isprivate
        owner_idle_time
        product
        qa_contact
        reporter
        reporter_accessible
        see_also
        short_desc
        status_whiteboard

        deadline
        estimated_time
        percentage_complete
        remaining_time
        work_time
    );
    push(@fields, 'votes') if QA::Util::get_config()->{test_extensions};

    return @fields;
}

use constant STANDARD_SELECT_FIELDS =>
    qw(bug_severity bug_status op_sys priority rep_platform resolution);

use constant ALL_SELECT_FIELDS => (STANDARD_SELECT_FIELDS,
    qw(cf_qa_status cf_single_select));
use constant PRODUCT_FIELDS => qw(version target_milestone component);
use constant ALL_FIELDS => (GLOBAL_GENERAL_FIELDS, ALL_SELECT_FIELDS,
                            PRODUCT_FIELDS);
use constant MANDATORY_FIELDS => qw(short_desc product version component);

use constant PUBLIC_PRODUCT  => 'Another Product';
use constant PRIVATE_PRODUCT => 'QA-Selenium-TEST';

sub get_field {
    my ($fields, $field) = @_;
    return first { $_->{name} eq $field } @$fields;
}

sub get_products_from_field {
    my $field = shift;
    my %products;
    foreach my $value (@{ $field->{values} }) {
        foreach my $vis_value (@{ $value->{visibility_values} }) {
            $products{$vis_value} = 1;
        }
    }
    return \%products;
}

our %field_ids;
foreach my $rpc (@clients) {
    my $call = $rpc->bz_call_success('Bug.fields');
    my $fields = $call->result->{fields};
    foreach my $field (ALL_FIELDS) {
        my $field_data = get_field($fields, $field);
        ok($field_data, "$field is in the returned result")
            or diag(Dumper($fields));
        $field_ids{$field} = $field_data->{id};

        if (grep($_ eq $field, MANDATORY_FIELDS)) {
            ok($field_data->{is_mandatory}, "$field is mandatory");
        }
        else {
            ok(!$field_data->{is_mandatory}, "$field is not mandatory");
        }
    }

    foreach my $field (ALL_SELECT_FIELDS, PRODUCT_FIELDS) {
        my $field_data = get_field($fields, $field);
        ok(defined $field_data->{visibility_values},
           "$field has visibility_values defined");
        my $field_vis_undefs = grep { !defined $_ }
                                    @{ $field_data->{visibility_values} };
        is($field_vis_undefs, 0, "$field.visibility_values has no undefs")
          or diag(Dumper($field_data->{visibility_values}));

        ok(defined $field_data->{values},
           "$field has 'values' defined");
        my $num_values = scalar @{ $field_data->{values} };
        ok($num_values, "$field has $num_values values");
        # The first bug status is a fake one and has no name, so we choose the 2nd item.
        my $first_value = $field_data->{values}->[1];
        ok(defined $first_value->{name}, 'The first value has a name')
            or diag(Dumper($field_data->{values}));
        # The sortkey for milestones can be negative.
        cmp_ok($first_value->{sortkey}, '=~', qr/^-?\d+$/,
               "The first value has a numeric sortkey");

        ok(defined $first_value->{visibility_values},
           "$field has visibilty_values defined on its first value")
            or diag(Dumper($field_data->{values}));
        my @value_visibility_values = map { @{ $_->{visibility_values} } }
                                      @{ $field_data->{values} };
        my $undefs = grep { !defined $_ } @value_visibility_values;
        is($undefs, 0,
           "$field.values.visibility_values has no undefs");
    }

    foreach my $field (PRODUCT_FIELDS) {
        my $field_data = get_field($fields, $field);
        is($field_data->{value_field}, 'product',
           "The value_field for $field is 'product'");
        my $products = get_products_from_field($field_data);
        ok($products->{+PUBLIC_PRODUCT},
           "$field values are returned for the public product");
        ok(!$products->{+PRIVATE_PRODUCT},
           "No $field values are returned for the private product");
    }
}

my @all_tests = (
    { args => { ids   => [values %field_ids],
                names => [ALL_FIELDS] },
                test => 'Getting all fields by name and id simultaneously',
                count => scalar ALL_FIELDS
    },
    { args  => { names => [INVALID_FIELD_NAME] },
      error => "There is no field named",
      test  => 'Invalid field name'
    },
    { args  => { ids => [INVALID_FIELD_ID] },
      error => 'must be numeric',
      test  => 'Invalid field id'
    },
    { user  => 'QA_Selenium_TEST',
      args  => { names => [PRODUCT_FIELDS] },
      test  => 'Getting product-specific fields as a privileged user',
      count => scalar PRODUCT_FIELDS,
      product_private_values => 1
    },
);

foreach my $field (ALL_FIELDS) {
    push(@all_tests,
         { args => { names => [$field] },
           test => "Logged-out users can get the $field field by name" });
    push(@all_tests,
         { args => { ids => [$field_ids{$field}] },
           test => "Logged-out users can get the $field by id" });
}

sub post_success {
    my ($call, $t) = @_;
    my $fields = $call->result->{fields};
    my $count = $t->{count};
    $count = 1 if !defined $count;
    is(scalar @$fields, $count, "Exactly $count field(s) returned");

    if ($t->{product_private_values}) {
        foreach my $field (@$fields) {
            my $name = $field->{name};
            my $field_data = get_field($fields, $name);
            my $products = get_products_from_field($field_data);
            ok($products->{+PUBLIC_PRODUCT},
               "$name values are returned for the public product");
            ok($products->{+PRIVATE_PRODUCT},
               "$name values are returned for the private product");
        }
    }
}

foreach my $rpc (@clients) {
    $rpc->bz_run_tests(tests => \@all_tests,  method => 'Bug.fields',
                       post_success => \&post_success);
}
