# ABSTRACT: Utilities for the testrail command line functions, and their main loops.
# PODNAME: TestRail::Utils

package TestRail::Utils;

use strict;
use warnings;

use Carp qw{confess cluck};
use Pod::Usage ();
use TestRail::API;

use IO::Interactive::Tiny ();
use Term::ANSIColor 2.01 qw(colorstrip);
use Term::ReadKey ();
use Scalar::Util qw{blessed};

=head1 SCRIPT HELPER FUNCTIONS

=head2 help

Print the perldoc for $0 and exit.

=cut

sub help {
    Pod::Usage::pod2usage( '-verbose' => 2, '-noperldoc' => 1, '-exitval' => 'NOEXIT' );
    return 0;
}

=head2 userInput

Wait for user input and return it.

=cut

sub userInput {
    my ( $echo_ok ) = @_;
    my $input = "";

    # I'm going to be super paranoid here and consider everything to be sensitive info by default.
    Term::ReadKey::ReadMode('noecho') unless $echo_ok;
    {
        local $SIG{'INT'} = sub { Term::ReadKey::ReadMode(0); exit 130; };
        $input = <STDIN>;
        chomp($input) if $input;
    }
    Term::ReadKey::ReadMode(0) unless $echo_ok;
    print "\n";
    return $input;
}

=head2 interrogateUser($options,@keys)

Wait for specified keys via userInput, and put them into $options HASHREF, if they are not already defined.
Returns modified $options HASHREF.
Dies if the user provides no value.

=cut

sub interrogateUser {
    my ($options,@keys) = @_;
    foreach my $key (@keys) {
        if (!$options->{$key}) {
            print "Type the $key for your TestRail install below:\n";
            $options->{$key} = TestRail::Utils::userInput( $key ne 'password' );
            die "$key cannot be blank!" unless $options->{$key};
        }
    }
    return $options;
}

=head2 parseConfig(homedir)

Parse .testrailrc in the provided home directory.

Returns:

ARRAY - (apiurl,password,user)

=cut

sub parseConfig {
    my ($homedir,$login_only) = @_;
    my $results = {};
    my $arr =[];

    open(my $fh, '<', $homedir . '/.testrailrc') or return (undef,undef,undef);#couldn't open!
    while (<$fh>) {
        chomp;
        @$arr = split(/=/,$_);
        if (scalar(@$arr) != 2) {
            warn("Could not parse $_ in '$homedir/.testrailrc'!\n");
            next;
        }
        $results->{lc($arr->[0])} = $arr->[1];
    }
    close($fh);
    return ($results->{'apiurl'},$results->{'password'},$results->{'user'}) if $login_only;
    return $results;
}

=head2 getFilenameFromTapLine($line)

Analyze TAP output by prove and look for filename boundaries (no other way to figure out what file is run).
Long story short: don't end 'unknown' TAP lines with any number of dots if you don't want it interpreted as a test name.
Apparently this is the TAP way of specifying the file that's run...which is highly inadequate.

Inputs:

STRING LINE - some line of TAP

Returns:

STRING filename of the test that output the TAP.

=cut

sub getFilenameFromTapLine {
    my $orig = shift;

    $orig =~ s/ *$//g; # Strip all trailing whitespace

    #Special case
    my ($is_skipall) = $orig =~ /(.*)\.+ skipped:/;
    return $is_skipall if $is_skipall;

    my @process_split = split(/ /,$orig);
    return 0 unless scalar(@process_split);
    my $dotty = pop @process_split; #remove the ........ (may repeat a number of times)
    return 0 if $dotty =~ /\d/; #Apparently looking for literal dots returns numbers too. who knew?
    chomp $dotty;
    my $line = join(' ',@process_split);

    #IF it ends in a bunch of dots
    #AND it isn't an ok/not ok
    #AND it isn't a comment
    #AND it isn't blank
    #THEN it's a test name

    return $line if ($dotty =~ /^\.+$/ && !($line =~ /^ok|not ok/) && !($line =~ /^# /) && $line);
    return 0;
}

=head2 TAP2TestFiles(file)

Returns ARRAY of TAP output for the various test files therein.
file is optional, will read TAP from STDIN if not passed.

=cut

sub TAP2TestFiles {
    my $file = shift;
    my ($fh,$fcontents,@files);

    if ($file) {
        open($fh,'<',$file);
        while (<$fh>) {
            $_ = colorstrip($_); #strip prove brain damage

            if (getFilenameFromTapLine($_)) {
                push(@files,$fcontents) if $fcontents;
                $fcontents = '';
            }
            $fcontents .= $_;
        }
        close($fh);
        push(@files,$fcontents) if $fcontents;
    } else {
        #Just read STDIN, print help if no file was passed
        die "ERROR: no file passed, and no data piped in! See --help for usage.\n" if IO::Interactive::Tiny::is_interactive();
        while (<>) {
            $_ = colorstrip($_); #strip prove brain damage
            if (getFilenameFromTapLine($_)) {
                push(@files,$fcontents) if $fcontents;
                $fcontents = '';
            }
            $fcontents .= $_;
        }
        push(@files,$fcontents) if $fcontents;
    }
    return @files;
}

=head2 getRunInformation

Return the relevant project definition, plan, run and milestone definition HASHREFs for the provided options.

Dies in the event the project/plan/run could not be found.

=cut

sub getRunInformation {
    my ($tr,$opts) = @_;
    confess("First argument must be instance of TestRail::API") unless blessed($tr) eq 'TestRail::API';

    my $project = $tr->getProjectByName($opts->{'project'});
    confess "No such project '$opts->{project}'.\n" if !$project;

    my ($run,$plan);

    if ($opts->{'plan'}) {
        $plan = $tr->getPlanByName($project->{'id'},$opts->{'plan'});
        confess "No such plan '$opts->{plan}'!\n" if !$plan;
        $run = $tr->getChildRunByName($plan,$opts->{'run'}, $opts->{'configs'});
    } else {
        $run = $tr->getRunByName($project->{'id'},$opts->{'run'});
    }

    confess "No such run '$opts->{run}' matching the provided configs (if any).\n" if !$run;

    #If the run/plan has a milestone set, then return it too
    my $milestone;
    my $mid = $plan ? $plan->{'milestone_id'} : $run->{'milestone_id'};
    if ($mid) {
        $milestone = $tr->getMilestoneByID($mid);
        confess "Could not fetch run milestone!" unless $milestone; #hope this doesn't happen
    }

    return ($project, $plan, $run, $milestone);
}

=head2 getHandle(opts)

Convenience method for binaries and testing.
Returns a new TestRail::API when passed an options hash such as is built by most of the binaries,
or returned by parseConfig.

Has a special 'mock' hash key that can only be used by those testing this distribution during 'make test'.

=cut

sub getHandle {
    my $opts = shift;

    $opts->{'debug'} = 1 if ($opts->{'browser'});

    my $tr = TestRail::API->new($opts->{apiurl},$opts->{user},$opts->{password},$opts->{'encoding'},$opts->{'debug'});
    if ($opts->{'browser'}) {
        $tr->{'browser'} = $opts->{'browser'};
        $tr->{'debug'} = 0;
    }
    return $tr;
}

1;

__END__

=head1 SPECIAL THANKS

Thanks to cPanel Inc, for graciously funding the creation of this module.
