package LIMS::Controller::User;

use strict;

use base 'LIMS::Base';

use Moose;
with (
	'LIMS::Controller::Roles::User', # generate_new_password
);

use LIMS::Local::Utils;

#-------------------------------------------------------------------------------
# called by 'Forgotten password' link on login page
sub password_change : Runmode {
    my $self = shift; $self->_debug_path($self->get_current_runmode);

    # get email address from query or return to login:
    my $email = $self->query->param('email_address')
        || $self->redirect( $self->query->url . '/login' ); # $self->debug($email);

    # args for User::get_user_details method:
    my %args_to_get = ( col => 'email', value => $email );

    # get user details from users table using email address from query:
    my $user = $self->stash->{user_details}
        = $self->model('User')->get_user_details(\%args_to_get); # $self->debug($user);

    # if email address not recognised, set message in flash & redirect to login:
    if (! $user) {
        $self->flash( email_address => $email, 'login' ); # use 3rd arg to 'scope'
        my $redirect = $self->query->url;
        
        # if external user, need to capture access token for re-direct:
        if ( my $access_token = $self->query->param('access_token') ) {
            $redirect .= '?access_token=' . $self->query->escape($access_token);
        }
        return $self->redirect( $redirect );
    }

    # create new 10-digit randomised letter/number password:
    my $new_password = $self->stash->{new_password}
        = $self->generate_new_password; # $self->debug($new_password);

    # encrypt new_password using SHA1 (or MD5):
    my $sha1 = LIMS::Local::Utils::sha1_digest($new_password); # $self->debug('sha1:'.$digest);

    # args for User::update_password() method:
    my %args_to_update = (
        id  => $user->id,
        pwd => $sha1,
    );

    # execute update_password method:
    my $rtn = $self->model('User')->update_password(\%args_to_update);

    return $self->error($rtn)if $rtn;

    # send new password details to registered email address:
    $self->_email_password;

    return $self->tt_process;
}

#-------------------------------------------------------------------------------
# Resources / Change password, or redirect from password_reset using forgotten password email link:
sub change_password : Runmode {
    my $self = shift; $self->_debug_path($self->get_current_runmode);

    # get js validation foo_onsubmit & foo_dfv_js vars into tt_params:
    $self->js_validation_profile('js_change_pwd');

    return $self->tt_process;
}

#-------------------------------------------------------------------------------
sub delete_message : Runmode {
    my $self = shift; $self->_debug_path($self->get_current_runmode);
    
    my $msg_id = $self->param('id');
    
    if ($msg_id) {
        my $rtn = $self->model('User')->delete_user_message($msg_id);
        if ($rtn) {
            $self->flash( error => $rtn );
        }
        else {
            $self->flash( info => $self->messages('user')->{msg_deleted} );
        }
    }
    return $self->redirect( $self->query->url . '/resources/user_messages' );
}

#-------------------------------------------------------------------------------
sub acknowledge_messages : Runmode {
    my $self = shift;
    
    my @message_ids = $self->query->param('msg_id');
    my $rtn = $self->model('User')->acknowledge_messages(\@message_ids);
    return $rtn
        ? $self->error($rtn)
        : $self->redirect( $self->query->url . '/resources/user_messages' );
}

#-------------------------------------------------------------------------------
# submission of change password form
sub do_change_password : Runmode {
    my $self = shift; $self->_debug_path($self->get_current_runmode);

    my $data = $self->validate('change_password'); # $self->debug($data);

    my $result = $self->form_validator($data);
    return $self->forward('change_password') if $result->has_error;

    my $vars = $result->valid; # $self->debug($vars);

    my $profile = $self->user_profile; # warn Dumper $profile

    # get current pwd from profile:
    my $profile_pwd = $profile->{password}; # $self->debug($profile_pwd);

    # SHA1 digest param old_password:
    my $param_password = LIMS::Local::Utils::sha1_digest($vars->{old_password});
        # $self->debug($param_password);

    # check param old_password matches users' profile pwd:
    unless ( $param_password eq $profile_pwd ) {
        $self->flash( error => $self->messages('user')->{old_pwd_mismatch} );
        return $self->redirect( $self->query->url . '/user/change_password' );
    }

    my $new_pwd = LIMS::Local::Utils::sha1_digest($vars->{new_password});

    # args for User::update_password() method:
    my %args_to_update = (
        id  => $profile->{id},
        pwd => $new_pwd,
    );

    # execute update_password method:
    my $rtn = $self->model('User')->update_password(\%args_to_update);

    return $self->error($rtn) if $rtn;

    # clear logged-in status to force re-login:
    $self->authen->logout;

    $self->flash( info => $self->messages('user')->{pwd_change_success} );
    return $self->redirect( $self->query->url );
}

#-------------------------------------------------------------------------------
# from hello page:
sub change_email : Runmode {
    my $self = shift; $self->_debug_path($self->get_current_runmode);
    my $errs = shift;

    # get js validation foo_onsubmit & foo_dfv_js vars into tt_params:
    $self->js_validation_profile('change_user_email');
    return $self->tt_process($errs);
}

#-------------------------------------------------------------------------------
sub do_change_email : Runmode {
    my $self = shift; $self->_debug_path($self->get_current_runmode);
    
    # retrieve user_id or complain:
    my $user_id = $self->param('id')
        or return $self->error( 'no id passed to ' . $self->get_current_runmode );

    my $profile = $self->validate('change_user_email');
    my $dfv = $self->check_rm( 'change_email', $profile )
	|| return $self->dfv_error_page;
    
    my $data = $dfv->valid;
    
    my $user = $self->model('User')->get_user_profile($user_id);
    $data->{user_profile} = $user; # add to $data for model
    
    { # check pwd OK:
        my $encrypted_pwd = LIMS::Local::Utils::sha1_digest($data->{password});
        
        unless ( $user->password eq $encrypted_pwd ) {
            $self->flash( error => $self->messages('user')->{pwd_incorrect} );
            return $self->forward('change_email');
        }
    }
    
    my $rtn = $self->model('User')->update_email($data); # empty if success    
    if ($rtn) { return $self->error($rtn) }
    
    # have to manually update email param in session:
    my $user_profile = $self->session->param('UserProfile');
    $user_profile->{email} = $data->{email};
    # re-save it:
    $self->session->param('UserProfile', $user_profile);
  
    $self->flash( info => $self->messages('action')->{edit_success} );
    return $self->redirect( $self->query->url . '/user/change_email' );
}

#-------------------------------------------------------------------------------
sub change_location : Runmode {
    my $self = shift; $self->_debug_path($self->get_current_runmode);
    my $errs = shift;
    
    my $user_locations
        = $self->model('User')->get_user_locations({sort_by => 'location_name'});
    $self->tt_params( user_locations => $user_locations );
    
    # create locations map for template:
    my %locations_map = map { $_->id => $_->location_name } @$user_locations;
    $self->tt_params( locations_map => \%locations_map );
    
    $self->js_validation_profile('change_user_location');
    return $self->tt_process($errs);
}

#-------------------------------------------------------------------------------
sub do_change_location : Runmode {
    my $self = shift; $self->_debug_path($self->get_current_runmode);
    
    # retrieve user_id or complain:
    my $user_id = $self->param('id')
        or return $self->error( 'no id passed to ' . $self->get_current_runmode );

    my $profile = $self->validate('change_user_location');
    my $dfv = $self->check_rm( 'change_location', $profile )
	|| return $self->dfv_error_page;
    
    my $data = $dfv->valid;
    
    my $user = $self->model('User')->get_user_profile($user_id);
    $data->{user_profile} = $user; # add to $data for model
    
    { # check pwd OK:
        my $encrypted_pwd = LIMS::Local::Utils::sha1_digest($data->{password});
        
        unless ( $user->password eq $encrypted_pwd ) {
            $self->flash( error => $self->messages('user')->{pwd_incorrect} );
            return $self->forward('change_location');
        }
    }
    
    my $rtn = $self->model('User')->update_location($data); # empty if success    
    if ($rtn) { return $self->error($rtn) }
    
    # have to manually update user_location_id param in session:
    my $user_profile = $self->session->param('UserProfile');
    $user_profile->{user_location_id} = $data->{location_id};
    # re-save it:
    $self->session->param('UserProfile', $user_profile);
  
    $self->flash( info => $self->messages('action')->{edit_success} );
    return $self->redirect( $self->query->url . '/user/change_location' );
}

#-------------------------------------------------------------------------------
# called by forgotten password email link
sub password_reset : Runmode {
    my $self = shift; $self->_debug_path($self->get_current_runmode);

    # from email-supplied user/password_reset/foo:
    my $new_pwd_id = $self->param('id')
        or die 'no id passed to password_reset'; # $self->debug($new_pwd_id);

    # retrieve new_passwd_data param from session:
    my $new_passwd_data = $self->session->param('new_passwd_data'); # $self->debug($new_passwd_data);

    my $session_pwd_id = ($new_passwd_data)
        ? $new_passwd_data->{id}
        : ''; # $new_passwd_data will not exist if session >24 hrs old so deleted
    
    # make sure unique id supplied in email matches identifier in session param:
    unless ( $new_pwd_id eq $session_pwd_id ) {
        $self->flash( info => $self->messages('login')->{session_id_not_found} );
        # redirect to login to display flash message:
        return $self->redirect( $self->query->url );
    }

    my @params = qw( user passwd destination ); # user in reqd authen_username format

    # create a redirect url from session new_passwd_data params (set flag for
    # cgiapp_prerun to skip re-direct after login - change pwd does that anyway):
    my $url = sprintf $self->query->url
        . '/login?authen_username=%s;authen_password=%s;destination=%s;skip_redirect=1',
            @{$new_passwd_data}{@params};

    # redirect to automatic login, destination = change_passwd:
    return $self->redirect( $url );
}

#-------------------------------------------------------------------------------
sub _email_password {
    my $self = shift; $self->_debug_path();

    # get user & pwd from stashed password_change():
    my $user = $self->stash->{user_details}; # warn Dumper $user;
    my $pwd  = $self->stash->{new_password};

    # create a unique identifier for email; later check for match in session data:
    my $unique_identifier = unpack "H*", pack "NS", time, $$;

    my $username = join '.', $user->first_name, $user->last_name;
    
    { # hash for session param 'new_passwd_data':
        my $destination = $self->query->url . '/user/change_password/' . $pwd;
        my %data = (
            destination => $destination,
            passwd      => $pwd,
            user        => $username,
            id          => $unique_identifier,
        );    
        # save %data to session using new_passwd_data param:
        $self->session->param( new_passwd_data => \%data );
    }
    
    # link for e-mail to take user to User::password_reset method - contains
    # session unique identifier so username, new passwd & destination can be
    # retrieved from session storage:
    my $link = 'user/password_reset/' . $unique_identifier;

    my %args = (
        hilis_url => $link,
        password  => $pwd,
        user      => $user,
    );
    my $msg = $self->tt_process('user/reset_password.tt', \%args);
	my $message_body = LIMS::Local::Utils::deindent(${$msg}); # deref 1st

    my %mail_data = (
        recipient => $user->email,
        config    => $self->cfg('settings'),
        subject   => 'Your new HILIS password',
        message   => $message_body,
    ); # $self->debug(\%mail_data); return 0;

    my $rtn = $self->model('Email')->send_message(\%mail_data); # Return::Value object

    # TODO: handle smtp timeout to not die
    return $self->error($rtn->string) if $rtn->type ne 'success';
}

1;