package password_requests;

use strict;
use warnings;
no warnings 'redefine';

use Data::Dumper;
use Session::Token();

# table:   calcms_password_requests
our @EXPORT_OK = qw(get insert delete get_columns);

use mail;
use uac;
use db;
use auth;

sub get_columns ($) {
    my ($config) = @_;
    my $dbh     = db::connect($config);
    return db::get_columns_hash($dbh, 'calcms_password_requests');
}

sub get ($$) {
    my ($config, $condition) = @_;

    my $dbh = db::connect($config);
    my @conditions  = ();
    my @bind_values = ();

    if (defined $condition->{user}) {
        push @conditions,  'user=?';
        push @bind_values, $condition->{user};
    }

    if (defined $condition->{token}) {
        push @conditions,  'token=?';
        push @bind_values, $condition->{token};
    }

    return undef if (scalar @conditions) == 0;

    my $conditions = " where " . join(" and ", @conditions);
    my $query = qq{
        select *
        from   calcms_password_requests
        $conditions
    };

    my $entries = db::get($dbh, $query, \@bind_values);
    return $entries->[0] || undef;
}

sub update($$) {
    my ($config, $entry) = @_;

    return unless defined $entry->{user};

    my $dbh         = db::connect($config);
    my @keys        = sort keys %$entry;
    my $values      = join(",", map { $_ . '=?' } @keys);
    my @bind_values = map { $entry->{$_} } @keys;
    push @bind_values, $entry->{token};

    my $query = qq{
        update calcms_password_requests
        set    $values
        where  token=?
    };
    db::put($dbh, $query, \@bind_values);
}

sub insert ($$) {
    my ($config, $entry) = @_;

    return undef unless defined $entry->{user};

    my $dbh = db::connect($config);
    return db::insert($dbh, 'calcms_password_requests', $entry);
}

sub delete ($$) {
    my ($config, $condition) = @_;

    my @conditions  = ();
    my @bind_values = ();

    if ((defined $condition->{user}) && ($condition->{user} ne '')) {
        push @conditions,  'user=?';
        push @bind_values, $condition->{user};
    }

    if ((defined $condition->{token}) && ($condition->{token} ne '')) {
        push @conditions,  'token=?';
        push @bind_values, $condition->{token};
    }

    return if (scalar @conditions) == 0;
    my $conditions = " where " . join(" and ", @conditions);

    my $dbh = db::connect($config);

    my $query = qq{
        delete
        from calcms_password_requests
        $conditions
    };

    db::put($dbh, $query, \@bind_values);
}

sub sendToken ($$) {
    my ($config, $entry) = @_;

    return undef unless defined $entry->{user};

    my $user = uac::get_user($config, $entry->{user});
    return undef unless defined $user;

    # check age of existing entry
    my $oldEntry = password_requests::get($config, { user => $entry->{user} });
    if (defined $oldEntry) {
        my $createdAt = $oldEntry->{created_at};
        my $age = time() - time::datetime_to_time($createdAt);
        if ($age < 60) {
            print STDERR "too many requests\n";
            return undef;
        }
        print STDERR "age=$age\n";
    }
    password_requests::delete($config, $entry);

    $entry->{max_attempts} = 0;
    $entry->{token}        = Session::Token->new->get;

    my $baseUrl = $config->{locations}->{source_base_url} . $config->{locations}->{editor_base_url};
    my $url     = $baseUrl . "/request-password.cgi?token=" . $entry->{token};
    my $content = "Hi,$user->{full_name}\n\n";
    $content .= "Someone has just tried to reset your password for $baseUrl.\n\n";
    $content .= "If you want to set a new password, please follow this link\n";
    $content .= $url . "\n\n";
    $content .= "If you do not want to set a new password, please ignore this mail.\n";

    mail::send(
        {
            "From"    => $config->{locations}->{email},
            "To"      => $user->{email},
            "Subject" => "request to change password for $baseUrl",
            "Data"    => $content
        }
    );

    password_requests::insert($config, $entry);
}

sub changePassword ($$$) {
    my ($config, $request, $userName) = @_;

    my $params      = $request->{params}->{checked};
    my $permissions = $request->{permissions};

    unless ((defined $userName) || ($userName eq '')) {
        return { error => 'The User could not be found.' };
    }

    my $user = uac::get_user($config, $userName);

    unless ((defined $user) && (defined $user->{id}) && ($user->{id} ne '')) {
        return { error => 'Te User ID could not be found.' };
    }

    if (my $msg = password_requests::isPasswordInvalid($params->{user_password})) {
        return { error => $msg } if $msg;
    }

    if ($params->{user_password} ne $params->{user_password2}) {
        return { error => 'The passwords entered do not match.' };
    }

    my $crypt = auth::crypt_password($params->{user_password});
    $user = { id => $user->{id} };
    $user->{salt} = $crypt->{salt};
    $user->{pass} = $crypt->{crypt};

    local $config->{access}->{write} = 1;
    my $result = uac::update_user($config, $user);
    return { success => "The password was changed for $userName." };
}

sub isPasswordInvalid($) {
    my ($password) = @_;
    unless (defined $password || $password eq '') {
        return "The password must not be empty.";
    }
    if (length($password) < 8) {
        return "The password must have at least 8 characters.";
    }
    unless ($password =~ /[a-z]/) {
        return "The password must contain at least one small character.";
    }
    unless ($password =~ /[A-Z]/) {
        return "The password must contain at least one big character";
    }
    unless ($password =~ /[0-9]/) {
        return "The password must contain at least one number.";
    }
    unless ($password =~ /[^a-zA-Z0-9]/) {
        return "The password must contain at least one special character.";
    }
    return 0;
}

#do not delete last line!
1;
