package GoogleCalendarApi;

use strict;
use warnings;

use JSON;
use JSON::WebToken;
use LWP::UserAgent;
use HTML::Entities;
use URI::Escape;
use Data::Dumper;
use DateTime;
use Time::HiRes qw(sleep);

use Common ( 'info', 'error' );

sub new {
    my $class  = shift;
    my $params = shift;

    #print Dumper($class);
    my $self = {};
    for my $attr ( 'calendarId', 'debug' ) {
        $self->{$attr} = $params->{$attr} if defined $params->{$attr};
    }
    $self->{debug} = 1;

    my $instance = bless $self, $class;
    if ( ( defined $params->{serviceAccount} ) && ( defined $params->{privateKey} ) ) {
        $instance->login( $params->{serviceAccount}, $params->{privateKey} );
    }

    return $instance;
}

sub setCalendar {
    my $self       = shift;
    my $calendarId = shift;
    $self->{calendarId} = $calendarId;
}

sub getBasicUrl {
    my $self = shift;
    return 'https://www.googleapis.com/calendar/v3/calendars/' . encode_entities( $self->{calendarId} );
}

#https://developers.google.com/google-apps/calendar/v3/reference/events/list

#returns {
#          'timeZone' => 'Europe/Berlin',
#          'description' => "Radioprogramm von Pi Radio f\x{fc}r 88vier.de",
#          'defaultReminders' => [],
#          'accessRole' => 'owner',
#          'etag' => '"1415821582086000"',
#          'kind' => 'calendar#events',
#          'summary' => '88vier.de Pi Radio (Programm)',
#          'updated' => '2014-11-12T19:46:22.086Z',
#          'items' => [...]
# }
sub getEvents {
    my $self   = shift;
    my $params = shift;

    my $url = '/events?';
    for my $param (
        'iCalUID',                'alwaysIncludeEmail', 'maxAttendees',            'maxResults',
        'orderBy',                'pageToken',          'privateExtendedProperty', 'q',
        'sharedExtendedProperty', 'showDeleted',        'showHiddenInvitations',   'singleEvents',
        'syncToken',              'timeZone'
      )
    {
        $url .= '&' . $param . '=' . uri_escape( $params->{$param} ) if defined $params->{$param};
    }
    for my $param ( 'timeMin', 'timeMax', 'updatedMin' ) {
        $url .= '&' . $param . '=' . uri_escape( $self->formatDateTime( $params->{$param} ) ) if defined $params->{$param};
    }
    my $result = $self->httpRequest( 'GET', $url );
    return $result;
}

# sleep 0.25 seconds to prevent hitting the 5.0 requests/second/user rate
#sub sleep{
#    my $this=shift;
#    my $duration=shift;
#    $duration=1 unless defined $duration;
#    select(undef, undef, undef, $duration);
#}

#https://developers.google.com/google-apps/calendar/v3/reference/events/delete
sub deleteEvent {
    my $self    = shift;
    my $eventId = shift;
    my $url     = '/events/' . $eventId;

    #DELETE https://www.googleapis.com/calendar/v3/calendars/calendarId/events/eventId
    my $result = $self->httpRequest( 'DELETE', $url );

    #$self->sleep();
    return $result;
}

#https://developers.google.com/google-apps/calendar/v3/reference/events/insert
sub insertEvent {
    my $self   = shift;
    my $params = shift;

    my $event = {
        start => {
            dateTime => $self->formatDateTime( $params->{start} )
        },
        end => {
            dateTime => $self->formatDateTime( $params->{end} )
        },
        summary     => $params->{summary}     || '',
        description => $params->{description} || '',
        location    => $params->{location}    || '',
        status      => $params->{confirmed}   || 'confirmed'
    };
    $event = encode_json $event;

    #POST https://www.googleapis.com/calendar/v3/calendars/calendarId/events
    my $url = '/events';
    my $result = $self->httpRequest( 'POST', $url, $event );

    #$self->sleep();
    return $result;
}

# send a HTTP request
sub httpRequest {
    my $self    = shift;
    my $method  = shift;
    my $url     = shift;
    my $content = shift || '';

    sleep 0.3;
    print STDERR "$method " . $url . "\n" if $self->{debug};

    die("missing url")        unless defined $url;
    die("calendarId not set") unless defined $self->{calendarId};
    die("not logged in ")     unless defined $self->{api};

    #prepend basic url including calendar id
    $url = $self->getBasicUrl() . $url;
    print STDERR "$method " . $url . "\n" if $self->{debug};

    my $response = undef;
    if ( $method eq 'GET' ) {
        $response = $self->{api}->get($url);
    } elsif ( ( $method eq 'POST' ) || ( $method eq 'PUT' ) ) {
        #return;
        print STDERR $content . "\n" if $self->{debug};
        my $request = HTTP::Request->new( $method, $url );
        $request->header( 'Content-Type' => 'application/json' );
        $request->content($content);
        $response = $self->{api}->request($request);
    } elsif ( $method eq 'DELETE' ) {
        #return;
        $response = $self->{api}->delete($url);
    }

    if ( $response->is_success ) {
        my $content = $response->content;
        return {} if $content eq '';
        return decode_json($content);
    } else {
        print "ERROR:\n";
        print "Code:    " . $response->code . "\n";
        print "Message: " . $response->message . "\n";
        print $response->content . "\n";
        die;
    }
}

# write datetime object to string
sub formatDateTime {
    my $self = shift;
    my $dt   = shift;

    my $datetime = $dt->format_cldr("yyyy-MM-ddTHH:mm:ssZZZZZ");
    print STDERR "$dt -> $datetime\n" if $self->{debug};
    return $datetime;
}

# parse datetime from string to object
sub getDateTime {
    my $self     = shift;
    my $datetime = shift;
    my $timezone = shift;

    return if ( !defined $datetime ) or ( $datetime eq '' );
    my @l = split /[\-\;T\s\:\+\.]/, $datetime;

    $datetime = DateTime->new(
        year      => $l[0],
        month     => $l[1],
        day       => $l[2],
        hour      => $l[3],
        minute    => $l[4],
        second    => $l[5],
        time_zone => $timezone
    );
    return $datetime;
}

# login with serviceAccount and webToken (from privateKey)
sub login {
    my $self           = shift;
    my $serviceAccount = shift;
    my $privateKey     = shift;

    # https://developers.google.com/accounts/docs/OAuth2ServiceAccount
    my $time = time;

    #create JSON Web Token
    my $jwt = JSON::WebToken->encode(
        {
            iss   => $serviceAccount,
            scope => 'https://www.googleapis.com/auth/calendar',
            aud   => 'https://accounts.google.com/o/oauth2/token',
            exp   => $time + 3600,
            iat   => $time,
        },
        $privateKey,
        'RS256',
        { typ => 'JWT' }
    );

    #send JSON web token to authentication service
    $self->{auth} = LWP::UserAgent->new();
    my $response = $self->{auth}->post(
        'https://accounts.google.com/o/oauth2/token',
        {
            grant_type => encode_entities('urn:ietf:params:oauth:grant-type:jwt-bearer'),
            assertion  => $jwt
        }
    );

    die( $response->code, "\n", $response->content, "\n" ) unless $response->is_success();
    my $data = decode_json( $response->content );

    #create a new user agent and set token to bearer
    $self->{api} = LWP::UserAgent->new();
    $self->{api}->default_header( Authorization => 'Bearer ' . $data->{access_token} );

    print STDERR "login successful\n" if $self->{debug};
    return $data;
}

1;
