#------------------------------------------------------------
# Copyright (C) 2008 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------
package Common::MFA::Util;

use strict;
use warnings;

use Exporter;
use POSIX;

use URI::Escape;
use MIME::Base64;
use WWW::Mechanize;
use JSON qw( decode_json encode_json);

our @ISA       = (qw(Exporter));
our @EXPORT    = (qw/generateOTP deleteQRFile generateQRCode sendMFAPassword isValidAuthenticatorCode sendSMSCode isValidSMSCode/);
our @EXPORT_OK = (qw/generateOTP deleteQRFile generateQRCode sendMFAPassword isValidAuthenticatorCode sendSMSCode isValidSMSCode/);

use Data::Dumper;

use List::Util qw/shuffle/;
use Authen::TOTP;
use Imager::QRCode;

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

use lib '/app/tools/common/lib';
use Common::Email;
use Common::RSApp;

use constant kCommonDirectory   => '/app/tools/common';
use constant kQRWebDirectory    => '/production/images/qr_codes/';
use constant kQRServerDirectory => kCommonDirectory . kQRWebDirectory;

# Twilio API base URL
use constant kTwilioAPI  => 'https://verify.twilio.com/v2';

use constant kTwilioAccountSID => 'AC8d9117bb56270e41f1270aaa1bd6a0c7';
use constant kTwilioAuthToken  => 'c97915bce4aa8cfc017ae81817ec621e';
use constant kTwilioSSID       => 'VA5fc3e1c4c286655dd98993be89f09be6';

# Generate a QR code that can be used to setup a user's authenticator APP
sub generateQRCode {
    my( $self, %args ) = @_;
    my $userID = $args{user_id};

    my $user = AppUser::DB::Item::User->Lookup( user_id => $userID );
    my $email   = $user->email;
    my $secret  = $user->mfa_secret;
    if ( !$secret ) {
        my $gen = new Authen::TOTP();
        $secret = $gen->gen_secret();
        $user->mfa_secret( $secret );
        $user->save;
    }

    my $gen = new Authen::TOTP(
        secret => $secret,
    );

    # The secret key is stored in RSCOMMON and will be different between the dev server(s) and production.
    # This means you will have to have separate entries for the same email in your authenticator app(s).
    # To help identify your authenticator app entries, we'll append the hostname to the "RoyaltyShare"
    # label in the authenticator app.  Note that this is for non-production entries only.  For production,
    # you will only see "RoyaltyShare" with the associated email address.
    #
    my $_issuer = 'RoyaltyShare';
    $_issuer   .= ' ' . Common::RSApp::GetRealHostname() if ( ! Common::RSApp::IsProductionServer() );
    my $issuer  = uri_escape($_issuer);

    # generate a TOTP URI, suitable to use in a QR Code
    my $uri = $gen->generate_otp(user => "$issuer:$email", secret => $secret, issuer => "$issuer");

    #print qq{$uri\n};

    my $qrcode = Imager::QRCode->new(
        size          => 4,
        margin        => 3,
        level         => 'L',
        casesensitive => 1,
        lightcolor    => Imager::Color->new(255, 255, 255),
        darkcolor     => Imager::Color->new(0, 0, 0),
    );


    # We tack the timestamp to prevent the browser from caching the QR image.
    # The user mfa_secret shouldn't change, but if it does then the correct QR
    # code will be displayed.
    my $filename = 'qr' . $userID . '_' . time() . '.png';
    my $file     = kQRServerDirectory . $filename; # full path to where we write the file

    my $webURI   = kQRWebDirectory . $filename;  # where the rendered page can access the image

    my $img      = $qrcode->plot($uri);
    $img->write(file => $file, type => "png") or die "Failed to write: " . $img->errstr;

    return $webURI;
}

# Generate a one time password for the specified user (use this for email-based verification)
sub generateOTP {
    my( $self, %args ) = @_;
    my $userID = $args{user_id};

    my @unique = (shuffle 0 .. 9)[0..5]; # 6 unique digits
    return join('',@unique);
}

# Generate and send a one-time password to the specified user
sub sendMFAPassword {
    my( $self, %args ) = @_;
    my $email = $args{email};

    my $user  = AppUser::DB::Item::User->Lookup( email => $email );
    my $code  = $self->generateOTP();

    my $now = strftime "%Y-%m-%d %H:%M:%S", localtime time;
    $user->mfa_date_created($now);
    $user->mfa_password($code);
    $user->save;

    # prepare and send the email
    my $to      = $email;
    my $from    = 'do-not-reply@royaltyshare.com';
    my $subject = "Authentication Code for RoyaltyShare";
    my $body    = "Dear $email,

Please enter the following code to verify your identify:

$code
";

    Common::Email->SendAWS(
        to      => $to,
        from    => $from,
        subject => $subject,
        body    => $body
    );

}

sub isValidAuthenticatorCode {
    my( $self, %args ) = @_;
    my $userID = $args{user_id};
    my $code   = $args{code};

    my $user   = AppUser::DB::Item::User->Lookup( user_id => $userID );
    my $secret = $user->mfa_secret;

    # sanity check - must be 6 digits and numeric
    if ( defined $code && (length($code) != 6 ||
       $code =~ /[^0-9]/ ) ) {
        return undef;
    }

    my $gen = new Authen::TOTP(
        secret => $secret,
    );

    if ( $gen->validate_otp(otp => $code, secret => $secret, tolerance => 1) ) {
        return 1;
    }
    return undef;
}

sub DeleteQRFile {
    my( $self, $uri ) = @_;
    my $localPath = kCommonDirectory . $uri;
    if ( -e $localPath ) {
        `rm $localPath`;
    }
}

sub sendSMSCode {
    my( $self, %args ) = @_;
    my $userID       = $args{user_id};
    my $user         = AppUser::DB::Item::User->Lookup( user_id => $userID );
    my $phoneNumber  = $user->mfa_phone;

    # Build the URL for the verification token
    my $url         = kTwilioAPI .  '/Services/' . kTwilioSSID . '/Verifications';

    my $mech        = WWW::Mechanize->new( autocheck => 0 );
    my $credentials = kTwilioAccountSID . ':' . kTwilioAuthToken;
    my $cred64      = encode_base64($credentials);

    $mech->add_header("authorization" => 'Basic ' . $cred64);
    my $response = $mech->post(
        $url,
        [
            'To'      => $phoneNumber,
            'Channel' => 'sms',
        ]
    );
    my $httpStatus   = $mech->status();
    my $decoded_json = decode_json( $mech->content() );
    print STDERR "D:--- Common/MFA/Util.sendSMSCode uid $userID  - response = ". Dumper($decoded_json) . "\n"; # XXX
    return { status => $httpStatus, status => $decoded_json->{status} };
}

sub isValidSMSCode {
    my( $self, %args ) = @_;
    my $userID       = $args{user_id};
    my $code         = $args{code};

    my $user         = AppUser::DB::Item::User->Lookup( user_id => $userID );
    my $phoneNumber  = $user->mfa_phone;

    # Build the URL for the verification token check
    my $url         = kTwilioAPI .  '/Services/' . kTwilioSSID . '/VerificationCheck';

    my $mech        = WWW::Mechanize->new( autocheck => 0 );
    my $credentials = kTwilioAccountSID . ':' . kTwilioAuthToken;
    my $cred64      = encode_base64($credentials);

    $mech->add_header("authorization" => 'Basic ' . $cred64);
    my $response = $mech->post(
        $url,
        [
            'To'      => $phoneNumber,
            'Code'    => $code,
        ]
    );
    my $httpStatus   = $mech->status();
    my $decoded_json = decode_json( $mech->content() );
    if ( $httpStatus == 200 ) {
        if ( $decoded_json->{status} eq 'approved' ) {
            return 1
        }
    }
    return undef;
}

###
1;#
###
