use strict;
use warnings;

use TestRail::API;
use Test::LWP::UserAgent::TestRailMock;

use Test::More tests => 71;
use Test::Fatal;
use Test::Deep;
use Scalar::Util ();

my $apiurl = $ENV{'TESTRAIL_API_URL'};
my $login  = $ENV{'TESTRAIL_USER'};
my $pw     = $ENV{'TESTRAIL_PASSWORD'};

#Mock if nothing is provided
my $is_mock = ( !$apiurl && !$login && !$pw );

like(
    exception { TestRail::API->new('trash'); },
    qr/invalid uri/i,
    "Non-URIs bounce constructor"
);

#XXX for some insane reason 'hokum.bogus' seems to be popular with cpantesters
my $bogoError = exception {
    TestRail::API->new( 'http://hokum.bogus', 'lies', 'moreLies', 0 );
};
SKIP: {
    skip(
        "Some CPANTesters like to randomly redirect all DNS misses to some other host, apparently",
        1
    ) if ( $bogoError =~ m/404/ );
    like(
        $bogoError,
        qr/Could not communicate with TestRail Server/i,
        "Bogus Testrail URI rejected"
    );
}

SKIP: {
    skip( "Testing authentication not supported with mock", 2 ) if ($is_mock);

    like(
        exception { TestRail::API->new( $apiurl, 'lies', 'moreLies', 0 ); },
        qr/Bad user credentials/i,
        "Bogus Testrail User rejected"
    );
    like(
        exception {
            TestRail::API->new( $apiurl, $login,
                'm043L13s                      ', 0 );
        },
        qr/Bad user credentials/i,
        "Bogus Testrail Password rejected"
    );
}

( $apiurl, $login, $pw ) =
  ( 'http://testrail.local', 'teodesian@cpan.org', 'fake' )
  if $is_mock;

my $tr = new TestRail::API( $apiurl, $login, $pw, 1 );

#Mock if necesary
$tr->{'debug'} = 0;
$tr->{'browser'} = $Test::LWP::UserAgent::TestRailMock::mockObject if $is_mock;

is( $tr->_doRequest('noSuchMethod'), -404, 'Requesting bad URI returns 404' );

#Test USER methods
my $userlist = $tr->getUsers();
ok( @$userlist, "Get Users returns list" );
my $myuser = $tr->getUserByEmail($login);
is( $myuser->{'email'}, $login, "Can get user by email" );
is( $tr->getUserByID( $myuser->{'id'} )->{'id'},
    $myuser->{'id'}, "Can get user by ID" );
is( $tr->getUserByName( $myuser->{'name'} )->{'name'},
    $myuser->{'name'}, "Can get user by Name" );

my @user_names = map { $_->{'name'} } @$userlist;
my @user_ids   = map { $_->{'id'} } @$userlist;
my @cuser_ids  = $tr->userNamesToIds(@user_names);
cmp_deeply( \@cuser_ids, \@user_ids, "userNamesToIds functions correctly" );
isnt( exception { $tr->userNamesToIds( @user_names, 'potzrebie' ); },
    undef, "Passing invalid user name throws exception" );

#Test PROJECT methods
my $project_name = 'CRUSH ALL HUMANS';

my $new_project =
  $tr->createProject( $project_name, 'Robo-Signed Soviet 5 Year Project' );
is( $new_project->{'name'}, $project_name, "Can create new project" );

ok( $tr->getProjects(), "Get Projects returns list" );
is( $tr->getProjectByName($project_name)->{'name'},
    $project_name, "Can get project by name" );
my $pjid = $tr->getProjectByID( $new_project->{'id'} );
is( Scalar::Util::reftype($pjid) eq 'HASH' ? $pjid->{'id'} : $pjid,
    $new_project->{'id'}, "Can get project by id" );

#Test TESTSUITE methods
my $suite_name = 'HAMBURGER-IZE HUMANITY';
my $new_suite  = $tr->createTestSuite( $new_project->{'id'}, $suite_name,
    "Robo-Signed Patriotic People's TestSuite" );
is( $new_suite->{'name'}, $suite_name, "Can create new testsuite" );

ok(
    $tr->getTestSuites( $new_project->{'id'} ),
    "Can get listing of testsuites for project"
);
is(
    $tr->getTestSuiteByName( $new_project->{'id'}, $new_suite->{'name'} )
      ->{'name'},
    $new_suite->{'name'},
    "Can get suite by name"
);
is( $tr->getTestSuiteByID( $new_suite->{'id'} )->{'id'},
    $new_suite->{'id'}, "Can get suite by id" );

#Test SECTION methods -- roughly analogous to TESTSUITES in TL
my $section_name = 'CARBON LIQUEFACTION';
my $new_section =
  $tr->createSection( $new_project->{'id'}, $new_suite->{'id'}, $section_name );
is( $new_section->{'name'}, $section_name, "Can create new section" );

ok( $tr->getSections( $new_project->{'id'}, $new_suite->{'id'} ),
    "Can get section listing" );
is(
    $tr->getSectionByName( $new_project->{'id'}, $new_suite->{'id'},
        $section_name )->{'name'},
    $section_name,
    "Can get section by name"
);
is( $tr->getSectionByID( $new_section->{'id'} )->{'id'},
    $new_section->{'id'}, "Can get new section by id" );

#Test CASE methods
my $case_name = 'STROGGIFY POPULATION CENTERS';
my $new_case = $tr->createCase( $new_section->{'id'}, $case_name );
is( $new_case->{'title'}, $case_name, "Can create new test case" );

ok(
    $tr->getCases(
        $new_project->{'id'}, $new_suite->{'id'}, $new_section->{'id'}
    ),
    "Can get case listing"
);
is(
    $tr->getCaseByName(
        $new_project->{'id'}, $new_suite->{'id'},
        $new_section->{'id'}, $case_name
      )->{'title'},
    $case_name,
    "Can get case by name"
);
is( $tr->getCaseByID( $new_case->{'id'} )->{'id'},
    $new_case->{'id'}, "Can get case by ID" );

#Test RUN methods
my $run_name = 'SEND T-1000 INFILTRATION UNITS BACK IN TIME';
my $new_run =
  $tr->createRun( $new_project->{'id'}, $new_suite->{'id'}, $run_name,
    "ACQUIRE CLOTHES, BOOTS AND MOTORCYCLE" );
is( $new_run->{'name'}, $run_name, "Can create new run" );

ok( $tr->getRuns( $new_project->{'id'} ), "Can get list of runs" );
is( $tr->getRunByName( $new_project->{'id'}, $run_name )->{'name'},
    $run_name, "Can get run by name" );
is( $tr->getRunByID( $new_run->{'id'} )->{'id'},
    $new_run->{'id'}, "Can get run by ID" );

#Test MILESTONE methods
my $milestone_name = "Humanity Exterminated";
my $new_milestone  = $tr->createMilestone(
    $new_project->{'id'}, $milestone_name,
    "Kill quota reached if not achieved in 5 years",
    time() + 157788000
);    #It IS a soviet 5-year plan after all :)
is( $new_milestone->{'name'}, $milestone_name, "Can create new milestone" );

ok( $tr->getMilestones( $new_project->{'id'} ), "Can get list of milestones" );
is(
    $tr->getMilestoneByName( $new_project->{'id'}, $milestone_name )->{'name'},
    $milestone_name,
    "Can get milestone by name"
);
is(
    $tr->getMilestoneByID( $new_milestone->{'id'} )->{'id'},
    $new_milestone->{'id'},
    "Can get milestone by ID"
);

#Test PLAN methods
my $plan_name = "GosPlan";
my $new_plan  = $tr->createPlan(
    $new_project->{'id'},
    $plan_name,
    "Soviet 5-year agriculture plan to liquidate Kulaks",
    $new_milestone->{'id'},
    [ { suite_id => $new_suite->{'id'}, name => "Executing the great plan" } ]
);
is( $new_plan->{'name'}, $plan_name, "Can create new plan" );

ok( $tr->getPlans( $new_project->{'id'} ), "Can get list of plans" );
my $namePlan = $tr->getPlanByName( $new_project->{'id'}, $plan_name );
is( $namePlan->{'name'}, $plan_name, "Can get plan by name" );
is( $tr->getPlanByID( $new_plan->{'id'} )->{'id'},
    $new_plan->{'id'}, "Can get plan by ID" );

#Get runs per plan, create runs in plan
my $prun = $new_plan->{'entries'}->[0]->{'runs'}->[0];
is(
    $tr->getRunByID( $prun->{'id'} )->{'name'},
    "Executing the great plan",
    "Can get child run of plan by ID"
);
is( $tr->getChildRunByName( $new_plan, "Executing the great plan" )->{'id'},
    $prun->{'id'}, "Can find child run of plan by name" );

SKIP: {
    skip(
        "Cannot create configurations programattically in the API like in mocks",
        2
    ) if !$is_mock;
    isnt(
        $tr->getChildRunByName(
            $namePlan, "Executing the great plan",
            ['testConfig']
        ),
        0,
        "Getting run by name returns child runs"
    );
    is(
        $tr->getChildRunByName( $namePlan, "Executing the great plan" ),
        0,
        "Getting run by name without sufficient configuration data returns child runs"
    );
}

#Test createRunInPlan
my $updatedPlan = $tr->createRunInPlan( $new_plan->{'id'}, $new_suite->{'id'},
    'Dynamic Plan Run' );
$prun = $updatedPlan->{'runs'}->[0];
is(
    $tr->getRunByID( $prun->{'id'} )->{'name'},
    "Dynamic Plan Run",
    "Can get newly created child run of plan by ID"
);

#Test TEST/RESULT methods
my $tests = $tr->getTests( $new_run->{'id'} );
ok( $tests, "Can get tests" );
is(
    $tr->getTestByName( $new_run->{'id'}, $tests->[0]->{'title'} )->{'title'},
    $tests->[0]->{'title'},
    "Can get test by name"
);
is(
    $tr->getTestByID( $tests->[0]->{'id'} )->{'id'},
    $tests->[0]->{'id'},
    "Can get test by ID"
);

my $resTypes    = $tr->getTestResultFields();
my $statusTypes = $tr->getPossibleTestStatuses();
ok( $resTypes,    "Can get test result fields" );
ok( $statusTypes, "Can get possible test statuses" );
my @status_names = map { $_->{'name'} } @$statusTypes;
my @status_ids   = map { $_->{'id'} } @$statusTypes;
my @computed_ids = $tr->statusNamesToIds(@status_names);
cmp_deeply( \@computed_ids, \@status_ids,
    "statusNamesToIds functions correctly" );
isnt( exception { $tr->statusNamesToIds( @status_names, 'potzrebie' ); },
    undef, "Passing invalid status name throws exception" );

#TODO make more thorough tests for options, custom options
my $result = $tr->createTestResults(
    $tests->[0]->{'id'},
    $statusTypes->[0]->{'id'},
    "REAPER FORCES INBOUND"
);
ok( defined( $result->{'id'} ), "Can add test results" );
my $results = $tr->getTestResults( $tests->[0]->{'id'} );
is( $results->[0]->{'id'}, $result->{'id'}, "Can get results for test" );

#Test status and assignedto filtering
my $filteredTests = $tr->getTests( $new_run->{'id'}, [ $status_ids[0] ] );
is( scalar(@$filteredTests), 1, "Test Filtering works: status id positive" );
$filteredTests = $tr->getTests( $new_run->{'id'}, [ $status_ids[1] ] );
is( scalar(@$filteredTests), 0, "Test Filtering works: status id negative" );
$filteredTests = $tr->getTests(
    $new_run->{'id'},
    [ $status_ids[0] ],
    [ $userlist->[0]->{'id'} ]
);
is( scalar(@$filteredTests), 0,
    "Test Filtering works: status id positive, user id negative" );
$filteredTests =
  $tr->getTests( $new_run->{'id'}, undef, [ $userlist->[0]->{'id'} ] );
is( scalar(@$filteredTests), 0,
    "Test Filtering works: status id undef, user id negative" );

#XXX there is no way to programmatically assign things :( so this will remain somewhat uncovered

#Get run summary
my $runs = $tr->getRuns( $new_project->{'id'} );
my ($summary) = $tr->getRunSummary(@$runs);    #I only care about the first one
isnt( $summary->{'run_status'}, undef, "Can get run statuses correctly" );
is(
    $summary->{'run_status'}->{'passed'},
    int( !$is_mock ),
    "Gets # of passed cases correctly"
);
is( $summary->{'run_status'}->{'untested'},
    int($is_mock), "Gets # of untested cases correctly" );

#Test configuration methods
my $configs = $tr->getConfigurations( $new_project->{'id'} );
my $is_arr  = is( Scalar::Util::reftype($configs),
    'ARRAY', "Can get configurations for a project" );
my ( @config_names, @config_ids );
if ($is_arr) {
    @config_names = map { $_->{'name'} } @$configs;
    @config_ids   = map { $_->{'id'} } @$configs;
}
my $t_config_ids =
  $tr->translateConfigNamesToIds( $new_project->{'id'}, \@config_names );
@config_ids    = sort(@config_ids);
@$t_config_ids = sort(@$t_config_ids);
is_deeply( \@config_ids, $t_config_ids,
    "Can correctly translate Project names to IDs" );

############################################################
# TestRail arbitrarily limits many calls to 250 result sets.
# Let's make sure our getters actually get everything.
############################################################
SKIP: {
    skip( "Skipping slow tests...", 2 ) if $ENV{'TESTRAIL_SLOW_TESTS'};

    #Check get_plans
    foreach my $i ( 0 .. $tr->{'global_limit'} ) {
        $tr->createPlan( $new_project->{'id'}, $plan_name, "PETE & RE-PIOTR" );
    }
    is(
        scalar( @{ $tr->getPlans( $new_project->{'id'} ) } ),
        ( $tr->{'global_limit'} + 2 ),
        "Can get list of plans beyond " . $tr->{'global_limit'}
    );

    #Check get_runs
    foreach my $i ( 0 .. $tr->{'global_limit'} ) {
        $tr->createRun( $new_project->{'id'}, $new_suite->{'id'}, $run_name,
            "ACQUIRE CLOTHES, BOOTS AND MOTORCYCLE" );
    }
    is(
        scalar( @{ $tr->getRuns( $new_project->{'id'} ) } ),
        ( $tr->{'global_limit'} + 2 ),
        "Can get list of runs beyond " . $tr->{'global_limit'}
    );
}
##########
# Clean up
##########

#Delete a plan
ok( $tr->deletePlan( $new_plan->{'id'} ), "Can delete plan" );

#Delete a milestone
ok( $tr->deleteMilestone( $new_milestone->{'id'} ), "Can delete milestone" );

#Delete a run
ok( $tr->deleteRun( $new_run->{'id'} ), "Can delete run" );

#Delete a case
ok( $tr->deleteCase( $new_case->{'id'} ), "Can delete Case" );

#Delete a section
ok( $tr->deleteSection( $new_section->{'id'} ), "Can delete Section" );

#Delete a testsuite
ok( $tr->deleteTestSuite( $new_suite->{'id'} ), "Can delete TestSuite" );

#Delete project now that we are done with it
ok( $tr->deleteProject( $new_project->{'id'} ), "Can delete project" );

1;
