#------------------------------------------------------------
# Copyright (C) 2006 RoyaltyShare, Inc.   All Rights Reserved
# $Id$
#------------------------------------------------------------
package API::Session;

#
# API Session management
#

use strict;
use warnings;

use Digest::MD5 qw(md5_hex);

use lib '/app/tools/appuser/lib';
use AppUser::DB::Item::User;
use AppUser::DB::Item::UserToken;

use lib '/app/tools/common/lib';
use Common::Log;
use Common::Util qw(secondsToDateTime dateTimeToSeconds);

use lib '/app/tools/api/lib';
use API::DB::Item::PortalSession;


use constant SESSION_EXPIRE_SECONDS => 86400;
use constant CURRENT_USER_COOKIE    => "RS_API";
use constant USER_TOKEN_COOKIE      => "userToken";
use constant CURRENT_DOMAIN         => ".royaltyshare.com";
use constant PORTAL_CLIENT_ID       => 998;

sub _clientIP {
    my $self = shift;
    my $r =
      $ENV{MOD_PERL_API_VERSION} >= 2
      ? Apache2::RequestUtil->request
      : Apache->request();

    my $forwardedFor = $r->header_in('X-Forwarded-For');
    my $ip = $ENV{REMOTE_ADDR};
    if ( $forwardedFor ) {
        ($ip) = $forwardedFor =~ /([^,\s]+)$/;
    }
    return $ip;
}

# commits a cookie to the apache headers
sub SetCookie {
    my $self   = shift;
    my $cookie = shift;

    my $r =
      $ENV{MOD_PERL_API_VERSION} >= 2
      ? Apache2::RequestUtil->request
      : Apache->request();

    $r->header_out->add( "Set-Cookie" => $cookie );
    return 1;
}

# get a cookie by name
sub GetCookie {
    my $self        = shift;
    my $cookie_name = shift;

    return undef if ( !defined $cookie_name || $cookie_name eq "" );

    my %cookies = CGI::Cookie->fetch;
    return $cookies{$cookie_name};
}

# delete a cookie(s) by name
sub DelCookie {
    my ($self, @cookiesNames) = @_;
    return unless @cookiesNames;

    my %cookies = CGI::Cookie->fetch;
    foreach my $name (@cookiesNames) {
        next unless exists $cookies{$name};
        $cookies{$name}->value('');
        $cookies{$name}->expires('-1d');
        $cookies{$name}->domain(CURRENT_DOMAIN);
        $self->SetCookie( $cookies{$name}->as_string );
    }

    return 1;
}

# IsValid is used for determining if a session is valid
sub IsValid {
    my $self = shift;

    my $user_cookie = $self->GetCookie(CURRENT_USER_COOKIE);
    return undef if ( !defined $user_cookie );

    my $cookie_val = $user_cookie->value();
    return undef if ( !defined $cookie_val );

    # cookie_val looks something like '123-somehashthingy'
    my ( $session_id, $checksum ) = split( '-', $cookie_val );
    return undef if ( !defined $session_id );  # no session key received

    my $dbItem = API::DB::Item::PortalSession->Lookup( id => $session_id );
    return undef if ( !defined $dbItem || !defined $dbItem->id );

    my $dateTimeSeconds = dateTimeToSeconds( $dbItem->date_created() );

    my $hash_check = md5_hex( $session_id, $dateTimeSeconds, _clientIP(), RSApache::DBSession::_secret_text() );
    return undef if ( $hash_check ne $checksum );

    if ( $dateTimeSeconds + SESSION_EXPIRE_SECONDS < time() ) {
        # the cookie is too old
        print STDERR "D: API cookie has expired\n";
        $dbItem->delete;
        return undef;
    }

    # This is a valid session.
    # Update the session record with the current time so we can tell
    # just _how_ active a particular session may be.
    #

    my $dateCreated = $dbItem->date_created;
    $dbItem->last_access(Common::DB::Item::kDateTimeNow);
    $dbItem->save();

    # XXX workaround to prevent date_created from getting updated due to 'kDateTimeNow'
    $dbItem->date_created($dateCreated);
    $dbItem->save();
    return 1;

}

sub GetSessionID {
    my $self   = shift;
    my $user_cookie = $self->GetCookie(CURRENT_USER_COOKIE);
    return undef if ( !defined $user_cookie );

    my $cookie_val = $user_cookie->value();
    return undef if ( !defined $cookie_val );

    # cookie_val looks something like '123-somehashthingy'
    my ( $session_id, $checksum ) = split( '-', $cookie_val );
    return $session_id;
}

sub _genSessionCookie {
    my $self   = shift;
    my $userID = shift;

    my $now    = time();
    my $ip     = $self->_clientIP();

    # does user have a session for the IP address?
    my $dateTimeSeconds;
    my $dbItem = API::DB::Item::PortalSession->Lookup( user_id => $userID, ip_address => $ip );
    if( $dbItem ) {

        $dateTimeSeconds = dateTimeToSeconds( $dbItem->date_created() );

        # delete all user_id sessions if the session has expired
        if ( $dateTimeSeconds + SESSION_EXPIRE_SECONDS < time() ) {

            $dbItem->delete;

            API::DB::Item::PortalSession->RemoveOldSessions( $userID );

            # Now we can establish the new session in the database.
            #
            $dbItem = API::DB::Item::PortalSession->Create();
            $dbItem->user_id( $userID );
            $dbItem->ip_address( $ip );
            $dbItem->date_created( secondsToDateTime($now) );
            $dbItem->save();
        } else {

            my $dateCreated = $dbItem->date_created;

            $dbItem->last_access(Common::DB::Item::kDateTimeNow);
            $dbItem->save();

            # XXX workaround to prevent date_created from getting updated due to 'kDateTimeNow'
            $dbItem->date_created($dateCreated);
            $dbItem->save();
        }
    } else {
        # session doesn't exist, create it

        $dbItem = API::DB::Item::PortalSession->Create();
        $dbItem->user_id( $userID );
        $dbItem->ip_address( $ip );
        $dbItem->date_created( secondsToDateTime($now) );
        $dbItem->save();

    }

    $dateTimeSeconds = dateTimeToSeconds( $dbItem->date_created() );

    my $session_id = $dbItem->id;

    Common::Log::Debug( "API SESSION_ID $session_id : " . $userID . " : $ip " );
    return undef unless defined $session_id;

    my $cookie_hash = md5_hex( $session_id, $dateTimeSeconds, $ip, RSApache::DBSession::_secret_text() );

    my $cookie_val = join( '-', $session_id, $cookie_hash );

    my $user_cookie = CGI::Cookie->new(
        -name    => API::Session::CURRENT_USER_COOKIE,
        -value   => $cookie_val,
        -domain  => CURRENT_DOMAIN,
        -expires => "+12M",
    );

    API::Session->SetCookie($user_cookie);

    return $cookie_val;
}

sub GetUserTokenCookie {
    my $self    = shift;
    my %cookies = CGI::Cookie->fetch;
    return $cookies{ API::Session::USER_TOKEN_COOKIE };
};

sub _genUserTokenCookie {
    my $self   = shift;
    my $email = shift;

    my $userToken = AppUser::DB::Item::UserToken->Lookup( email => $email );
    if ( !$userToken ) {
        $userToken = AppUser::DB::Item::UserToken->Create( email => $email );
    }

    $userToken->generateToken();
    $userToken->client_id(PORTAL_CLIENT_ID);
    $userToken->save;

    my $cookie_val = $userToken->token;

    my $user_cookie = CGI::Cookie->new(
        -name    => USER_TOKEN_COOKIE,
        -value   => $cookie_val,
        -domain  => CURRENT_DOMAIN,
        -expires => "+1d",
    );

    API::Session->SetCookie($user_cookie);

    return $cookie_val;
}

###
1;    # Play nicely.
###
