package App::BitBucketCli::Core;

# Created on: 2017-04-24 08:14:30
# Create by:  Ivan Wills
# $Id$
# $Revision$, $HeadURL$, $Date$
# $Revision$, $Source$, $Date$

use Moo;
use warnings;
use Carp;
use WWW::Mechanize;
use JSON::XS qw/decode_json encode_json/;
use Data::Dumper qw/Dumper/;
use English qw/ -no_match_vars /;
use App::BitBucketCli::Project;
use App::BitBucketCli::Repository;
use App::BitBucketCli::Branch;
use App::BitBucketCli::PullRequest;
use YAML::Syck qw/Dump/;

our $VERSION = 0.006;

has url => (
    is      => 'rw',
    builder => '_url',
    lazy    => 1,
);
has host => (
    is      => 'rw',
    required => 1,
);
has [qw/user pass/] => (
    is  => 'rw',
);
has mech => (
    is      => 'rw',
    default => sub { WWW::Mechanize->new },
);
has opt => (
    is      => 'rw',
    default => sub {{}},
);
has max => (
    is      => 'rw',
    default => 100,
);

sub projects {
    my ($self) = @_;
    my @projects;
    my $last_page = 0;
    my $next_page_start = 0;
    my $limit = 30;

    while ( ! $last_page ) {
        my $json;
        eval {
            $json = $self->_get($self->url . "/projects?limit=$limit&start=$next_page_start");
            1;
        } || do {
            warn "Couldn't list repositories: $@\n";
            return [];
        };
        push @projects, @{ $json->{values} };
        $last_page = $json->{isLastPage};
        $next_page_start = $json->{nextPageStart};
    }

    return map {App::BitBucketCli::Project->new($_)} @projects;
}

sub repositories {
    my ($self, $project) = @_;
    my @repositories;
    my $last_page = 0;
    my $next_page_start = 0;
    my $limit = 30;

    while ( ! $last_page ) {
        my $json;
        eval {
            $json = $self->_get($self->url . "/projects/$project/repos?limit=$limit&start=$next_page_start");
            1;
        } || do {
            warn "Couldn't list repositories: $@\n";
            return [];
        };
        push @repositories, @{ $json->{values} };
        $last_page = $json->{isLastPage};
        $next_page_start = $json->{nextPageStart};
    }

    return map {App::BitBucketCli::Repository->new($_)} @repositories;
}

sub repository {
    my ($self, $project, $repository) = @_;

    my $json;
    eval {
        $json = $self->_get($self->url . "/projects/$project/repos/$repository");
        1;
    } || do {
        warn "Couldn't get repository information $@\n";
        return [];
    };

    return $json;
}

sub pull_requests {
    my ($self, $project, $repository, $state) = @_;
    $state ||= 'OPEN';
    my @pull_requests;
    my $last_page = 0;
    my $next_page_start = 0;
    my $limit = 30;

    while ( ! $last_page ) {
        my $json;
        eval {
            $json = $self->_get($self->url . "/projects/$project/repos/$repository/pull-requests?limit=$limit&start=$next_page_start&state=$state");
            1;
        } || do {
            warn "Couldn't list pull_requests $@\n";
            return [];
        };
        push @pull_requests, @{ $json->{values} };
        $last_page = $json->{isLastPage};
        $next_page_start = $json->{nextPageStart};
        last if @pull_requests >= $self->max;
    }

    return map {App::BitBucketCli::PullRequest->new($_)} @pull_requests;
}

sub branch {
    my ($self, @branches) = @_;

    my $branches = $self->get_branches($self->opt->{project}, $self->opt->{repository});

    for my $branch (sort keys %{ $branches }) {
        next if !grep {$branch eq $_} @branches;
        print "$branch\n";
    }

    return;
}

sub get_pull_requests {
    my ($self, $project, $repository, $state) = @_;
    my $json;
    my @prs;
    $state = $state ? "?state=$state" : '';

    my $next = $self->_get_all("/projects/$project/repos/$repository/pull-requests$state");

    while ( my $pr = $next->() ) {
        push @prs, App::BitBucketCli::PullRequest->new($pr);
    }

    return \@prs;
}

sub get_branches {
    my ($self, $project, $repository) = @_;
    my @branches;
    my $next = $self->_get_all("/projects/$project/repos/$repository/branches?orderBy=MODIFICATION&details=true");

    while ( my $branch = $next->() ) {
        $branch->{project}    = $project;
        $branch->{repository} = $repository;
        push @branches, App::BitBucketCli::Branch->new($branch);
    }

    return \@branches;
}

sub _get_all {
    my ($self, $url) = @_;
    my $last_page = 0;
    my $next_page_start = 0;
    my $limit = 30;

    my $json;

    return sub {
        if ( $json && @{ $json->{values} } ) {
            return shift @{ $json->{values} };
        }
        if ($last_page) {
            return;
        }

        my $page_url = $self->url . $url . ( $url =~ /[?]/ ? '&' : '?' ) . "limit=$limit&start=$next_page_start";
        $json = $self->_get($page_url);

        $last_page = $json->{isLastPage};
        $next_page_start = $json->{nextPageStart};

        return shift @{ $json->{values} || [] };
    };
}

sub _get {
    my ($self, $url) = @_;

    warn "$url\n" if $ENV{BB_SHOW_URLS};
    $self->mech->get($url);

    return decode_json($self->mech->content);
}

sub _url {
    my ($self) = @_;
    my $url = "https://";
    if ( $self->user ) {
        $url .= _url_encode($self->user);

        if ( $self->pass ) {
            $url .= ':' . _url_encode($self->pass);
        }
        $url .= '@';
    }
    $url .= $self->host . "/rest/api/1.0";

    return $url;
}

sub _url_encode {
    my $str = shift;
    $str =~ s/(\W)/sprintf('%%%x',ord($1))/eg;
    return $str;
}

1;

__END__

=head1 NAME

App::BitBucketCli::Core - Library for talking to BitBucket Server (or Stash)

=head1 VERSION

This documentation refers to App::BitBucketCli::Core version 0.006


=head1 SYNOPSIS

   use App::BitBucketCli::Core;

   # create a stash object
   my $stash = App::BitBucketCli::Core->new(
       url => 'http://stash.example.com/',
   );

   # Get a list of open pull requests for a repository
   my $prs = $stash->pull_requests($project, $repository);

=head1 DESCRIPTION

=head1 SUBROUTINES/METHODS

=head2 C<branch ()>

=head2 C<get_branches ()>

=head2 C<get_pull_requests ()>

=head2 C<projects ()>

=head2 C<pull_requests ( $project, $repository, $state )>

Gets all of the pull requests in C<$state>.

=head2 C<repositories ( $project )>

Gets details of all repositories of $project

=head2 C<repository ( $project, $repository )>

Gets details of a repository

=head1 ATTRIBUTES

=head2 url

=head2 host

=head2 user

=head2 pass

=head2 mech

=head2 max

Set the maximum number of results to collect from BitBucket Server.

=head2 opt

=head1 DIAGNOSTICS

=head1 CONFIGURATION AND ENVIRONMENT

=head1 DEPENDENCIES

=head1 INCOMPATIBILITIES

=head1 BUGS AND LIMITATIONS

There are no known bugs in this module.

Please report problems to Ivan Wills (ivan.wills@gmail.com).

Patches are welcome.

=head1 AUTHOR

Ivan Wills - (ivan.wills@gmail.com)

=head1 LICENSE AND COPYRIGHT

Copyright (c) 2017 Ivan Wills (14 Mullion Close, Hornsby Heights, NSW Australia 2077).
All rights reserved.

This module is free software; you can redistribute it and/or modify it under
the same terms as Perl itself. See L<perlartistic>.  This program is
distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

=cut
