#!/usr/bin/env perl

# see perldoc below for instructions on how to use this script.

use FindBin ();
use lib "$FindBin::RealBin/../lib";

use Cwd qw( abs_path );
use File::Basename qw( dirname );
use Storable qw( store );
use Term::Prompt qw( prompt );
use Type::Params qw( signature );
use Types::Standard qw( Str StrMatch ArrayRef Optional );
use YAML::Any qw( Dump LoadFile );

use Google::RestApi::Auth::OAuth2Client ();

# set the scope for the project you are working one.
# for this package, drive and spreadsheets are required.
my @scope = (
  'https://www.googleapis.com/auth/calendar',
  'https://www.googleapis.com/auth/documents',
  'https://www.googleapis.com/auth/drive',
  'https://www.googleapis.com/auth/gmail.modify',
  'https://www.googleapis.com/auth/spreadsheets',
  'https://www.googleapis.com/auth/tasks',
);

my $config_file = $ARGV[0] or die "A config file name must be provided. See 'perldoc $0'.\n";
my $config = eval { LoadFile($config_file); };
die "Unable to load YAML config file '$config_file': $@\n" if $@;

print "Validating config:\n", Dump($config);
my $check = signature(
  bless => !!0,
  named => [
    class         => StrMatch[qr/^OAuth2Client$/],
    client_id     => Str,
    client_secret => Str,
    scope         => Optional[ArrayRef[Str]],
    token_file    => Str,
  ],
);
$check->(%{ $config->{auth} });

my $oauth2 = Google::RestApi::Auth::OAuth2Client->new(
  client_id     => $config->{auth}->{client_id},
  client_secret => $config->{auth}->{client_secret},
  scope         => \@scope,
);

# We need to set these parameters this way in order to ensure 
# that we get not only an access token, but also a refresh token
# that can be used to update it as needed. 
my $url = $oauth2->authorize_url(
  access_type     => 'offline',
  approval_prompt => 'force',
);

# Give the user instructions on what to do:
print <<END

The following URL can be used to obtain an access token from
Google.  

1. Copy the URL and paste it into a browser.  

2. You may be asked to log into your Google account if you 
were not logged in already in that browser. If so, go 
ahead and log in to whatever account you want to have 
access to the Google doc. 

3. On the next page, click "Accept" when asked to grant access. 

4. You will then be redirected to a page with a box in the 
left-hand column labeled  "Authorization code". Copy the code 
in that box and come back here.

Here is the URL to paste in your browser to get the code:

$url

END
    ;

# Here is where we get the code from the user:
my $code = prompt('x', 'Paste the code obtained at the above URL here: ', '', ''); 

# Exchange the code for an access token:
my $token = $oauth2->access_token($code)
  or die "Unable to exchange the code for an access token";

# If we get to here, it worked!  Report success: 
print "\nToken obtained successfully!\n";
print "Here are the token contents:\n\n";
print $token->to_string(), "\n\n";

# Save the token for future use:
my $token_file = dirname($config_file) . "/$config->{auth}->{token_file}";
store($token->session_freeze(), $token_file);

print <<END2

Token successfully stored in file $token_file.

END2
    ;

# Run the scope check to verify the token scopes and API enablement.
my $test_runner = "$FindBin::RealBin/../t/run_unit_tests.t";
if (!-f $test_runner) {
  print <<END_MISSING;

NOTE: The scope check test could not be found at:
  $test_runner

This check is only available when running from a cloned copy of the
repository. To verify that your token scopes and APIs are correctly
configured, clone the repo and run the check manually:

  git clone https://github.com/mvsjes2/p5-google-restapi.git
  cd p5-google-restapi
  GOOGLE_RESTAPI_CONFIG=$config_file \\
    TEST_CLASS='Test::Google::RestApi::ScopeCheck' \\
    prove -v t/run_unit_tests.t

A failing test will print the Google Cloud Console URL needed to
enable each missing API directly in the output.

END_MISSING
} else {
  print "Running scope check to verify token scopes and API enablement...\n\n";

  my $abs_config  = abs_path($config_file);
  local $ENV{GOOGLE_RESTAPI_CONFIG} = $abs_config;
  local $ENV{TEST_CLASS}            = 'Test::Google::RestApi::ScopeCheck';

  open(my $fh, '-|', $^X,
    "-I$FindBin::RealBin/../lib",
    "-I$FindBin::RealBin/../t/lib",
    "-I$FindBin::RealBin/../t/unit",
    $test_runner,
  ) or die "Cannot run scope check: $!";

  my (@failures, $last_failure);
  while (my $line = <$fh>) {
    print $line;
    if ($line =~ /^not ok \d+ - (.+)/) {
      $last_failure = { message => $1, url => undef };
      push @failures, $last_failure;
    } elsif ($last_failure && $line =~ /^#\s+(https?:\S+)/) {
      $last_failure->{url} = $1;
      $last_failure = undef;
    } elsif ($line !~ /^#/) {
      $last_failure = undef;
    }
  }
  close($fh);

  print "\n", "=" x 60, "\n";
  if (@failures) {
    printf "SCOPE CHECK: %d issue(s) found:\n\n", scalar @failures;
    for my $f (@failures) {
      print "  * $f->{message}\n";
      print "    $f->{url}\n" if $f->{url};
      print "\n";
    }
    print "Once resolved, re-run the scope check with:\n\n";
    print "  GOOGLE_RESTAPI_CONFIG=$abs_config \\\n";
    print "    TEST_CLASS='Test::Google::RestApi::ScopeCheck' \\\n";
    print "    prove -v t/run_unit_tests.t\n";
  } else {
    print "SCOPE CHECK: all APIs reachable. Your token is ready to use.\n";
  }
  print "=" x 60, "\n";
}

__END__

=head1 SYNOPSIS

Script to create an OAuth2 token that can be stored and used later to authorize
REST API access to your Google account.

Based on code from https://gist.github.com/hexaddikt/6738162

=head1 INITIAL GOOGLE CLOUD SETUP

Before running this script you need a Google Cloud project with OAuth credentials
and the relevant APIs enabled. This is a one-time setup per project.

=head2 1. Create a Google Cloud Project

=over

=item * Go to L<https://console.cloud.google.com> and sign in.

=item * Click the project dropdown at the top and select B<New Project>.

=item * Give it a name and click B<Create>.

=back

=head2 2. Enable the APIs You Need

=over

=item * In the left menu go to B<APIs & Services E<gt> Library>.

=item * Search for and enable each API you intend to use. For this package the
relevant APIs are:

=over

=item * Google Drive API

=item * Google Sheets API

=item * Google Calendar API

=item * Google Docs API

=item * Gmail API

=item * Tasks API

=back

You only need to enable the APIs you actually plan to use. Each failing scope
check test will print the exact Cloud Console URL to enable that specific API.

=back

=head2 3. Configure the OAuth Consent Screen

=over

=item * Go to B<APIs & Services E<gt> OAuth consent screen>.

=item * Choose B<External> and click B<Create>.

=item * Fill in the required fields (app name, support email) and click B<Save and Continue>
through the remaining steps.

=item * On the B<Test users> page, add the Google account(s) you will use for testing.
Only listed users can authorise your app while it remains in Testing mode.

=back

=head2 4. Create OAuth Credentials

=over

=item * Go to B<APIs & Services E<gt> Credentials>.

=item * Click B<+ Create Credentials> and select B<OAuth client ID>.

=item * Choose B<Desktop app>, give it a name, and click B<Create>.

=item * Copy the B<Client ID> and B<Client Secret> shown in the confirmation dialog.

=back

=head2 5. Create a Config File

Create a YAML file with your credentials:

    ---
    auth:
        class: OAuth2Client
        client_id: <client-id-from-google>
        client_secret: <client-secret-from-google>
        token_file: <filename-for-the-stored-token>  # filename only, not a path

The token file will be written to the same directory as this config file. The
same config file is used by this package at runtime to access the Google APIs.

=head2 6. Run This Script

    perl bin/google_restapi_oauth_token_creator /path/to/your/config.yaml

Follow the prompts:

=over

=item * Copy the printed URL and paste it into a browser.

=item * Log in to your Google account if prompted, then click B<Allow>.

=item * Copy the authorisation code from the resulting page and paste it back
into the terminal.

=back

The script will store the token and then run a scope check to confirm that all
enabled APIs are reachable. Any failing check will print the Cloud Console URL
needed to enable that API.

=head2 7. Use the Token

    use Google::RestApi;
    my $rest_api = Google::RestApi->new(config_file => '/path/to/your/config.yaml');

See L<Google::RestApi> for further details.

=head1 RE-RUNNING THE SCOPE CHECK

If you enable additional APIs later and want to verify without re-creating the
token, run:

    GOOGLE_RESTAPI_CONFIG=/path/to/your/config.yaml \
      TEST_CLASS='Test::Google::RestApi::ScopeCheck' \
      prove -v t/run_unit_tests.t

This requires a cloned copy of the repository. See the NOTE printed by this
script if the test file is not found.
