use strict;
use warnings;    ## required because I can't work out how to get percritic to use my modern config

package WebService::Google::Client::UserAgent;
$WebService::Google::Client::UserAgent::VERSION = '0.06';

# ABSTRACT: User Agent wrapper for working with Google APIs

use Moo;

extends 'Mojo::UserAgent';
use WebService::Google::Client::Credentials;
use WebService::Google::Client::AuthStorage;
use Mojo::UserAgent;
use Data::Dumper;    # for debug

# use Data::Printer;    # for debug - !PS is this used removing?

use Carp;

#has 'ua'                            => ( is => 'ro', default => sub { Mojo::UserAgent->new } ); ## why not extend and create a derviced class ?
has 'do_autorefresh'                => ( is => 'rw', default => 1 );    # if 1 storage must be configured
has 'auto_update_tokens_in_storage' => ( is => 'rw', default => 1 );
has 'debug'                         => ( is => 'rw', default => 0 );
has 'credentials' => ( is => 'rw', default => sub { WebService::Google::Client::Credentials->instance }, handles => [qw(access_token user auth_storage)], lazy => 1 );

## NB - used by both Client and Discovery

# Keep access_token in headers always actual

sub build_headers
{
  my ( $self ) = @_;

  # carp "".(caller(0))[3]."() : ".Dumper \@_;


  # p $self;
  my $headers = {};

  # carp "build_headers: ".$self->access_token;
  if ( $self->access_token )
  {
    $headers->{ 'Authorization' } = 'Bearer ' . $self->access_token;
    return $headers;
  }
  else
  {
    croak 'No access_token, cant build headers';
  }

}


sub build_http_transaction
{
  my ( $self, $params ) = @_;
  my $http_method   = uc( $params->{ httpMethod } ) || 'GET';                                                           # uppercase ?
  my $optional_data = $params->{ options }          || '';
  my $path          = $params->{ path }             || croak( 'path parameter required for build_http_transaction' );
  carp 'Attention! You are using POST, but no payload specified' if ( ( $http_method eq 'POST' ) && !defined $optional_data );
  carp "build_http_transaction:: $http_method $path " if $self->debug;
  croak "$http_method Not a SUPPORTED HTTP method parameter specified to build_http_transaction" . Dumper $params unless $http_method =~ /^GET|PATH|PUT|POST|PATCH|DELETE$/ixm;

  if ( $http_method =~ /^POST|PATH|PUT|PATCH$/ixg )
  {
    ## ternary conditional on whether optional_data is set
    return $optional_data eq ''
      ? $self->build_tx( $http_method => $path => $self->build_headers )
      : $self->build_tx( $http_method => $path => $self->build_headers => json => $optional_data );
  }
  else    ## DELETE or GET
  {
    return $self->build_tx( $http_method => $path => $self->build_headers => form => $optional_data ) if ( $http_method eq 'GET' );
    return $self->build_tx( $http_method => $path => $self->build_headers ) if ( $http_method eq 'DELETE' );
  }

  #return undef; ## assert: should never get here
}



# In case if access_token expired
# carp $response->message; # Unauthorized
# carp $response->json->{error}{message}; # Invalid Credentials
# carp $response->code; # 401
# carp $response->is_error; # 1

# my $res = $self->start($tx)->res->json;  # Mojo::Message::Response

# for future:
# if ( grep { $_->{message} eq 'Invalid Credentials' && $_->{reason} eq 'authError'} @{$res->{error}{errors}} ) { ... }

# carp "First api_query() result : ".Dumper $res if $self->debug;
# carp "Auto refresh:".$self->do_autorefresh;

# if ((defined $res->{error}) && ($self->autorefresh) && ($self->auth_storage->type) && ($self->auth_storage->path)) { # token expired error handling

# https://metacpan.org/pod/Mojo::Message::Response#code

# if ((defined $res->{error}) && ($self->do_autorefresh)) {

sub api_query
{
  my ( $self, $params ) = @_;

  my $res = $self->start( $self->build_http_transaction( $params ) )->res;
  ## TODO: HANDLE TIMEOUTS AND OTHER ERRORS IF THEY WEREN'T HANDLED BY build_http_transaction

  if ( ( $res->code == 401 ) && $self->do_autorefresh )
  {
    my $attempt = 1;    ## is this used? perhaps intent was to allow requery with delays on fail ?

    #while ($res->{error}{message} eq 'Invalid Credentials')  {
    while ( $res->code == 401 )
    {
      croak "No user specified, so cant find refresh token and update access_token" unless $self->user;
      carp "401 response - access_token was expired. Attemptimg to update it automatically ...";

      # carp "Seems like access_token was expired. Attemptimg update it automatically ..." if $self->debug;

      my $cred      = $self->auth_storage->get_credentials_for_refresh( $self->user );    # get client_id, client_secret and refresh_token
      my $new_token = $self->refresh_access_token( $cred )->{ access_token };             # here also {id_token} etc
      carp "api_query() Got a new token: " . $new_token if $self->debug;
      $self->access_token( $new_token );

      if ( $self->auto_update_tokens_in_storage )
      {
        $self->auth_storage->set_access_token_to_storage( $self->user, $self->access_token );
      }

      #$tx  = $self->build_http_transaction( $params );
      $res = $self->start( $self->build_http_transaction( $params ) )->res;               # Mojo::Message::Response
    }
  }
  elsif ( $res->code == 403 )
  {
    croak( 'Unexpected permission denied 403 error ' );
  }
  return $res if $res->code == 200;
  return {} if $res->code == 204;                                                         ## NO CONTENT - INDICATES OK FOR DELETE ETC
  carp( "unhandled api_query response code " . $res->code );
  return $res;
}


sub refresh_access_token
{
  my ( $self, $credentials ) = @_;

  if (
    ( !defined $credentials->{ client_id } )    ## could this be caught somewhere earlier than here?
    || ( !defined $credentials->{ client_secret } )    ##  unless credentials include an access token only ?
    || ( !defined $credentials->{ refresh_token } )
    )
  {
    croak 'If you credentials are missing the refresh_token - consider removing the auth at '
      . 'https://myaccount.google.com/permissions as The oauth2 server will only ever mint one refresh '
      . 'token at a time, and if you request another access token via the flow it will operate as if '
      . 'you only asked for an access token.'
      if !defined $credentials->{ refresh_token };

    croak "Not enough credentials to refresh access_token. Check that you provided client_id, client_secret and refresh_token";
  }

  carp "refresh_access_token:: Attempt to refresh access_token " if $self->debug;
  $credentials->{ grant_type } = 'refresh_token';
  return $self->post( 'https://www.googleapis.com/oauth2/v4/token' => form => $credentials )->res->json || croak( 'refresh_access_token failed' );    # tokens
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

WebService::Google::Client::UserAgent - User Agent wrapper for working with Google APIs

=head1 VERSION

version 0.06

=head1 METHODS

=head2 build_http_transaction

  Example of usage:

      $gapi->build_http_transaction({
        method => 'post',
        path => 'https://www.googleapis.com/calendar/users/me/calendarList',
        options => { key => value }
      })

=head2 refresh_access_token

Get new access token for user from Google API server

  $self->refresh_access_token({
		client_id     => '',
		client_secret => '',
		refresh_token => ''
	})

 Q: under what conditions could we not have a refresh token? - what scopes are required? ensure that included in defaults if they are req'd

=head2 api_query

Low-level method that can make any API query to a Google API service

Required params: method, route

$self->access_token must be valid

Examples of usage:

  $gapi->api_query({
      method => 'get',
      path => 'https://www.googleapis.com/calendar/users/me/calendarList',
    });

  $gapi->api_query({
      method => 'post',
      path => 'https://www.googleapis.com/calendar/v3/calendars/'.$calendar_id.'/events',
      options => { key => value }
  }

Returns L<Mojo::Message::Response> object

=head1 AUTHORS

=over 4

=item *

Pavel Serikov <pavelsr@cpan.org>

=item *

Peter Scott <peter@localshop.com.au>

=back

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2017-2018 by Pavel Serikov, Peter Scott and others.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
