package Thirdlane::Auth::OAuth2::Google;
use parent 'Thirdlane::Auth::OAuth2::Generic';

use utf8;
use strict;
use warnings;


use URI;
use Digest::MD5 qw(md5_hex);
use JSON::XS qw(encode_json decode_json);

use constant {
    DISCOVERY_URL => q{https://accounts.google.com/.well-known/openid-configuration}
};

sub discovery_url {
    my $self = shift;

    return DISCOVERY_URL;
}

sub get_access_token {
    my $self = shift;
    my ( $args ) = @_;

    my $result = { };

    my $access_token_query = {
	code => $args->{code},
	client_id => $self->{provider}->{client_id},
	client_secret => $self->{provider}->{client_secret},
	redirect_uri => $self->{provider}->{redirect_uri},
	grant_type => "authorization_code"
    };

    if ( defined $args ) {
	$access_token_query = $self->merge( $access_token_query, $args );
    }

    my $token_response = eval { $self->web_req( {
	method => "POST",
	url => $self->get_provider_data("token_endpoint"),
	body => [ %{ $access_token_query } ],
	json => 1
     } ) };

    if ( defined $token_response ) {
	$result->{access_token} = $token_response;

#	my $key_response = $self->cache->get('oauth::config::provider::' . $self->name . '::jwks');

#	unless ( defined $key_response ) {
	    my $key_response = eval { $self->web_req( {
		method => "GET",
		url => $self->get_provider_data("jwks_uri"),
		json => 1
	    } ) };
#	    $self->cache->set('oauth::config::provider::' . $self->name . '::jwks', $key_response, 60*60)
#		if defined $key_response;
#	}

	if ( defined $key_response ) {
	    $result->{public_keys} = $key_response;

	    my $verify_result = eval { $self->try_decode_jwt($token_response->{id_token}, $key_response) };

	    if ( $verify_result ) {
		$result->{id_token} = $verify_result;
	    }
	}
    }

    return $result;
}

sub refresh_access_token {
    my $self = shift;
    my ( $args ) = @_;

    my $result = { };

    my $refresh_token_query = {
	client_id => $self->{provider}->{client_id},
	client_secret => $self->{provider}->{client_secret},
	refresh_token => $args->{refresh_token},
	grant_type => "refresh_token"
    };

    if ( defined $args ) {
	$refresh_token_query = $self->merge( $refresh_token_query, $args );
    }

    my $token_response = eval { $self->web_req( {
	method => "POST",
	url => $self->get_provider_data("token_endpoint"),
	body => [ %{ $refresh_token_query } ],
	json => 1
     } ) };

    if ( defined $token_response ) {
	$result->{access_token} = $token_response;
    }

    return $result;
}

sub revoke_token {
    my $self = shift;
    my ( $args ) = @_;

    my $result = { };

    my $revoke_token_query = {
	token => $args->{token} || $args->{access_token} || $args->{refresh_token}
    };

#    if ( defined $args ) {
#	$revoke_token_query = $self->merge( $revoke_token_query, $args );
#    }

    $self->debug(["Revoke token: ", $args, "query", $revoke_token_query]);

    my $revoke_token_response = eval { $self->web_req( {
	method => "POST",
	url => $self->get_provider_data("revocation_endpoint"),
	body => [ %{ $revoke_token_query } ]
     } ) };

    $self->debug(["Revoke token result: ", $revoke_token_response]);

    if ( defined $revoke_token_response ) {
	return 1;
    }

    return undef;
}

sub authentication_url {
    my $self = shift;
    my ( $args ) = @_;

    my $uri = URI->new( $self->get_provider_data("authorization_endpoint") );

    my $authentication_query = {
	scope => "openid email profile https://www.googleapis.com/auth/contacts.readonly",
#	state => "state",
	prompt => "select_account", #none consent select_account ""
#	display => "", #page, popup, touch, and wap
#	login_hint => "", #email google id "sub" str
	access_type => "offline", #offline(refresh token)  online
	include_granted_scopes => "true",
	response_type => "code",

	client_id => $self->{provider}->{client_id},
	redirect_uri => $self->{provider}->{redirect_uri}
    };

    if ( defined $args ) {
	$authentication_query = $self->merge( $authentication_query, $args );
    }

    $uri->query_form( $authentication_query );

    return $uri->as_string;
}

sub authorization_url {
    my $self = shift;
    my ( $args ) = @_;

    my $uri = URI->new( $self->get_provider_data("authorization_endpoint") );

    my $authorization_query = {
	scope => "https://www.googleapis.com/auth/contacts.readonly",
#	state => "state",
	prompt => "select_account", #none consent select_account ""
#	login_hint => "", #email google id "sub" str
	access_type => "offline", #offline(refresh token)  online
	include_granted_scopes => "true",
	response_type => "code",

	client_id => $self->{provider}->{client_id},
	redirect_uri => $self->{provider}->{redirect_uri}
    };

    if ( defined $args ) {
	$authorization_query = $self->merge( $authorization_query, $args );
    }

    $uri->query_form( $authorization_query );

    return $uri->as_string;
}

sub signin_url {
    my $self = shift;
    my ( $args ) = @_;

    my $uri = URI->new( $self->get_provider_data("authorization_endpoint") );

    my $authentication_query = {
	scope => "openid email profile https://www.googleapis.com/auth/contacts.readonly",
#	state => "state",
#	prompt => "", #none consent select_account ""
#	display => "", #page, popup, touch, and wap
#	login_hint => "", #email google id "sub" str
	access_type => "offline", #offline(refresh token)  online
	include_granted_scopes => "true",
	response_type => "code",

	client_id => $self->{provider}->{client_id},
	redirect_uri => $self->{provider}->{redirect_uri}
    };

    if ( defined $args ) {
	$authentication_query = $self->merge( $authentication_query, $args );
    }

    $uri->query_form( $authentication_query );

    return $uri->as_string;
}

sub is_provider {
    my $self = shift;

    return 1;
}

sub get_access_token_from_context {
    my $self = shift;
    my ( $args ) = @_;

    return undef unless exists $args->{context} && defined $args->{context} && length $args->{context};

    my $auth_data = $self->get_auth_data( { context => $args->{context} } );

    if ( exists $auth_data->{ $args->{context} }->{authentications}->{access_token}->{string} ) {
        return $auth_data->{ $args->{context} }->{authentications}->{access_token}->{string};
    }

    return undef;
}

sub build_ua {
    my $self = shift;
    my ( $args ) = @_;

    $self->debug(["Building user agent args: ", $args]);

    return undef unless exists $args->{context} && defined $args->{context} && length $args->{context};

#    my $auth_data = $self->get_auth_data( { context => $args->{context} } );

#    if ( exists $auth_data->{ $args->{context} }->{authentications}->{access_token}->{string} ) {
#	$args->{access_token} = $auth_data->{ $args->{context} }->{authentications}->{access_token}->{string};
#    }

    my $ua = $self->get_ua();

    $ua->add_handler( request_prepare => sub {
	my($request, $ua, $h) = @_;
	$request->header( Authorization => 'Bearer ' . $self->get_access_token_from_context( { context => $args->{context} } ) ) if exists $args->{context};
	$self->debug(["User agent request: ", $request]);
	$self->debug(["User agent request auth data ", 'Bearer ' . $self->get_access_token_from_context( { context => $args->{context} } ), $args ]);
    });

    $ua->add_handler( response_done => sub {
	my($response, $ua, $h) = @_;
	$response->header( Tlprovider => $self->name );
	$self->debug(["User agent response: ", $response]);

	if ( defined $response ) {
	    my $res_data = $response->decoded_content();
	    $res_data = eval { decode_json( $res_data ) };

	    unless ( $@ ) {
		if ( defined $res_data && exists $res_data->{error} && exists $res_data->{error}->{status} ) {
		    if ( $res_data->{error}->{status} eq q{UNAUTHENTICATED} ) {
			$response->header( Tlintstatus => 'stale_access_token');
			$response->header( Tlintstatuscontext => $args->{context} );

			$self->debug(["Stale access_token detected"]);
		    }
		}
	    }
	}
    });

    $self->debug(["Building user agent finished: ", $ua]);

    return $ua;
}

1;

__END__

{                                                                           [56/1855]
  "error": {
    "code": 401,
    "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
    "status": "UNAUTHENTICATED"
  }
}