package Pcore::API::Bitbucket;

use Pcore -class, -res;

has username => ( required => 1 );
has password => ( required => 1 );

has _auth => ( is => 'lazy', init_arg => undef );

sub BUILDARGS ( $self, $args = undef ) {
    $args->{username} ||= $ENV->user_cfg->{BITBUCKET}->{username} if $ENV->user_cfg->{BITBUCKET}->{username};

    $args->{password} ||= $ENV->user_cfg->{BITBUCKET}->{password} if $ENV->user_cfg->{BITBUCKET}->{password};

    return $args;
}

sub _build__auth ($self) {
    return 'Basic ' . P->data->to_b64( "$self->{username}:$self->{password}", $EMPTY );
}

sub _req1 ( $self, $method, $endpoint, $data = undef ) {
    my $res = P->http->$method(
        'https://bitbucket.org/api/1.0' . $endpoint,
        headers => [
            Authorization  => $self->_auth,
            'Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8',
        ],
        data => $data ? P->data->to_uri($data) : undef
    );

    if ( !$res ) {
        return res [ $res->{status}, $res->{reason} ], $res->{data};
    }
    else {
        my $data = $res->{data} && $res->{data}->$* ? P->data->from_json( $res->{data} ) : undef;

        return res $res->{status}, $data;
    }
}

sub _req2 ( $self, $method, $endpoint, $data = undef ) {
    my $res = P->http->$method(
        'https://api.bitbucket.org/2.0' . $endpoint,
        headers => [
            Authorization  => $self->_auth,
            'Content-Type' => 'application/json',
        ],
        data => $data ? P->data->to_json($data) : undef
    );

    $data = $res->{data} && $res->{data}->$* ? P->data->from_json( $res->{data} ) : undef;

    if ( !$res ) {
        return res [ $res->{status}, $data->{error}->{message} // $res->{reason} ];
    }
    else {
        return res $res->{status}, $data;
    }
}

# https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D#post
sub create_repo ( $self, $repo_id, %args ) {
    %args = (

        # common atts
        description => undef,
        has_issues  => 1,
        has_wiki    => 1,
        is_private  => 0,

        # bitbucket attrs
        fork_police => 'allow_forks',    # allow_forks, no_public_forks, no_forks
        language    => 'perl',
        %args,
    );

    return $self->_req2( 'post', "/repositories/$repo_id", \%args );
}

# https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D#delete
sub delete_repo ( $self, $repo_id ) {
    return $self->_req2( 'delete', "/repositories/$repo_id", undef );
}

# VERSIONS
# https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D/versions
sub get_versions ( $self, $repo_id ) {
    my $versions;

    my $page = 1;

  GET_PAGE:
    my $res = $self->_req2( 'get', "/repositories/$repo_id/versions?page=$page&pagelen=100" );

    if ($res) {
        for my $ver ( $res->{data}->{values}->@* ) {
            $versions->{ $ver->{name} } = $ver->{links}->{self}->{href};
        }

        if ( $res->{data}->{next} ) {
            $page++;

            goto GET_PAGE;
        }
        else {
            return res 200, $versions;
        }
    }
    else {
        return $res;
    }
}

# https://confluence.atlassian.com/bitbucket/issues-resource-296095191.html#issuesResource-POSTanewversion
sub create_version ( $self, $repo_id, $ver ) {
    my $res = $self->_req1( 'post', "/repositories/$repo_id/issues/versions", { name => version->parse($ver)->normal } );

    if ( !$res && $res->{data}->$* =~ /already exists/sm ) {
        $res->set_status(200);
    }

    return $res;
}

# MILESTONES
# https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D/milestones
sub get_milestones ( $self, $repo_id ) {
    my $versions;

    my $page = 1;

  GET_PAGE:
    my $res = $self->_req2( 'get', "/repositories/$repo_id/milestones?page=$page&pagelen=100" );

    if ($res) {
        for my $ver ( $res->{data}->{values}->@* ) {
            $versions->{ $ver->{name} } = $ver->{links}->{self}->{href};
        }

        if ( $res->{data}->{next} ) {
            $page++;

            goto GET_PAGE;
        }
        else {
            return res 200, $versions;
        }
    }
    else {
        return $res;
    }
}

# https://confluence.atlassian.com/bitbucket/issues-resource-296095191.html#issuesResource-POSTanewmilestone
sub create_milestone ( $self, $repo_id, $ver ) {
    my $res = $self->_req1( 'post', "/repositories/$repo_id/issues/milestones", { name => version->parse($ver)->normal } );

    if ( !$res && $res->{data}->$* =~ /already exists/sm ) {
        $res->set_status(200);
    }

    return $res;
}

# https://confluence.atlassian.com/bitbucket/issues-resource-296095191.html#issuesResource-GETalistofissuesinarepository%27stracker
sub get_issues ( $self, $repo_id, %args ) {
    %args = (
        sort   => 'priority',    # priority, kind, version, component, milestone
        status => undef,
        start  => 0,
        limit  => 50,            # 50 - max.
        %args,
    );

    # remove undefined args
    for ( keys %args ) { delete $args{$_} if !defined $args{$_} }

    my $issues;

    my $res = $self->_req1( 'get', "/repositories/$repo_id/issues?" . P->data->to_uri( \%args ) );

    if ($res) {
        for my $issue ( $res->{data}->{issues}->@* ) {
            $issues->{ $issue->{local_id} } = $issue;
        }

        return res 200, data => $issues, total => $res->{data}->{count};
    }
    else {
        return $res;
    }
}

# https://confluence.atlassian.com/bitbucket/issues-resource-296095191.html#issuesResource-GETanindividualissue
sub get_issue ( $self, $repo_id, $issue_id ) {
    return $self->_req1( 'get', "/repositories/$repo_id/issues/$issue_id" );
}

# https://confluence.atlassian.com/bitbucket/issues-resource-296095191.html#issuesResource-Updateanexistingissue
sub update_issue ( $self, $repo_id, $issue_id, $data ) {
    return $self->_req1( 'put', "/repositories/$repo_id/issues/$issue_id", $data );
}

1;
## -----SOURCE FILTER LOG BEGIN-----
##
## PerlCritic profile "pcore-script" policy violations:
## +------+----------------------+----------------------------------------------------------------------------------------------------------------+
## | Sev. | Lines                | Policy                                                                                                         |
## |======+======================+================================================================================================================|
## |    3 | 201                  | Subroutines::ProhibitManyArgs - Too many arguments                                                             |
## +------+----------------------+----------------------------------------------------------------------------------------------------------------+
##
## -----SOURCE FILTER LOG END-----
__END__
=pod

=encoding utf8

=head1 NAME

Pcore::API::Bitbucket

=head1 SYNOPSIS

=head1 DESCRIPTION

=head1 ATTRIBUTES

=head1 METHODS

=head1 SEE ALSO

=cut