package WebApp;
# Simple web app example using Plack. To run it:
# plackup examples/oauth_web.psgi

use 5.14.1;
use Moo;
use Encode qw/encode_utf8/;
use HTML::Escape qw/escape_html/;
use Plack::Request;
use Plack::Response;
use Twitter::API;

has twitter_client => (
    is => 'ro',
    default => sub {
        Twitter::API->new_with_traits(
            traits => 'Enchilada',
            # Net::Twitter example app credentials:
             map(tr/A-Za-z/N-ZA-Mn-za-m/r, qw/
                 pbafhzre_xrl i8g3WVYxFglyotakTYBD
                 pbafhzre_frperg 5e31eFZp0ACgOcUpX8ZiaPYt2bNlSYk5rTBZxKZ
            /),
            # To use your own app credentials:
            # consumer_key    => 'your-app-key',
            # consumer_secret => 'your-app-secret',
        );
    },
);

# In a production application, use something like Redis to store request token
# secrets. Twitter expires request tokens after 15 minutes. Your app should
# keep them just a bit longer to ensure you don't discard them before Twitter
# does. Maybe 20 minutes. In this simple demo, we won't worry about expiration.
has secret_cache => (
    is => 'ro',
    default => sub { {} },
);

# We only need it once, so remove it from the cache
sub get_secret { delete $_[0]->secret_cache->{$_[1]} }
sub set_secret { $_[0]->secret_cache->{$_[1]} = $_[2] }

sub uri_for {
    my ( $self, $req, $path ) = @_;

    my $uri = $req->base;
    $uri->path($uri->path . $path);
    return $uri;
}

my %route = (
    '/'               => 'home_page',
    '/oauth_callback' => 'oauth_callback',
);

sub dispatch {
    my ( $self, $req, $res ) = @_;

    my $method = $route{$req->path} // 'not_found';
    $self->$method($req, $res);
}

sub handle_request {
    my ( $self, $env ) = @_;

    my $req = Plack::Request->new($env);
    my $res = Plack::Response->new(200);
    $res->content_type('text/html; charset=utf-8');

    $self->dispatch($req, $res);
    $res->finalize;
}

sub to_app {
    my $self = shift->new;
    sub { $self->handle_request(@_) };
};

sub home_page {
    my ( $self, $req, $res ) = @_;

    my $client = $self->twitter_client;

    # If we have access token/secret, display verify_credentials response
    if ( my $credentials = $req->cookies->{'dont-do-this-at-home'} ) {
        my ( $token, $secret ) = split /\s+/, $credentials;
        my $r = $client->verify_credentials({
            -token        => $token,
            -token_secret => $secret,
        });
        my $body = escape_html(
            $client->json_parser->pretty(1)->encode($r)
        );
        $res->body("<pre>$body</pre>");
    }
    else {
        # Otherwise, prompt the user to authenticate
        my $r = $client->oauth_request_token({
            callback => $self->uri_for($req, 'oauth_callback'),
        });
        my ( $token, $secret ) = @{$r}{qw/oauth_token oauth_token_secret/};

        # Save the request token and secret; we'll need them later.
        $self->set_secret($token, $secret);

        my $url = $client->oauth_authentication_url({
            oauth_token => $token,
        });
        $res->body(qq{<a href="$url">Authenticate with Twitter</a>});
    }
}

sub oauth_callback {
    my ( $self, $req, $res ) = @_;

    my $token    = $req->param('oauth_token');
    my $verifier = $req->param('oauth_verifier');

    if ( $token && $verifier ) {
        # The user authenticated!

        # Get our cached request token secret
        my $secret = $self->get_secret($token) // die 'missing secret';
        my $r = $self->twitter_client->oauth_access_token({
            token        => $token,
            token_secret => $secret,
            verifier     => $verifier,
        });

        # DON'T DO THIS AT HOME!
        #
        # In a production app, you will store the access_token and
        # access_token_secret in a database. They can be used to make Twitter
        # API calls on behalf of the authenticated user. Ideally, you should
        # treat them like you would user names and passwords. Encrypt them.
        #
        # For our simple demo, since we don't have a permanent data store,
        # we'll store them in a session cookie.
        $res->cookies->{'dont-do-this-at-home'} = join ' ',
            $$r{oauth_token}, $$r{oauth_token_secret};

        $res->redirect($self->uri_for($req, ''));
        return;
    }

    my $home = $self->uri_for($req, '');
    if ( $token = $req->param('denied') ) {
        # The user canceled the authentication request and select "return to
        # the application".
        $self->get_secret($token); # discard; no longer valid or useful
        $res->body(qq{You denied us access. <a href="$home">Go home</a>});
        return;
    }

    # /oauth_callback was requested without the expected parameters; let's just
    # redirect to the root page
    $res->redirect($home);
}

sub not_found {
    my ( $self, $req, $res ) = @_;

    my $home = $self->uri_for($req, '');
    $res->status(404);
    $res->body($req->path_info
        . qq{ does not live here, try <a href="$home">the main page</a>});
}

my $app = __PACKAGE__->to_app;