package NGCP::Panel::Utils::Form;

use Sipwise::Base;
use Data::Password::zxcvbn qw(password_strength);
use NGCP::Panel::Utils::Auth;

sub validate_password {
    my %params = @_;
    my $c = $params{c};
    my $field = $params{field};
    my $pass_change = $params{password_change};
    my $pw = $c->config->{security}{password};
    my $utf8 = $params{utf8} // 1;
    my $pass = $field->value;

    my $minlen = $pw->{min_length} // 12;
    my $maxlen = $pw->{max_length} // 40;

    my $is_sip_password = 0;
    my $is_web_password = 0;
    my $is_admin_password = 0;

    if ($params{admin}) {
        $is_admin_password = 1;
    } elsif ($field->name eq 'password') {
        $is_sip_password = 1;
    } elsif ($field->name eq 'webpassword') {
        $is_web_password = 1;
    } elsif ($field->name eq 'new_password') {
        if ($pass_change) {
            my $ngcp_realm = $c->request->env->{NGCP_REALM} // 'admin';
            if ($ngcp_realm eq 'admin') {
                $is_admin_password = 1;
            } else {
                $is_web_password = 1;
            }
        } else {
            $is_web_password = 1;
        }
    }

    if ($is_sip_password) {
        return unless $pw->{sip_validate};
    } elsif ($is_web_password || $is_admin_password) {
        return unless $pw->{web_validate};
    } else {
        return;
    }

    if(length($pass) < $minlen) {
        $field->add_error($c->loc('Must be at minimum [_1] characters long', $minlen));
    }
    if(length($pass) > $maxlen) {
        $field->add_error($c->loc('Must be at maximum [_1] characters long', $maxlen));
    }
    if ($pass =~ /\s/) {
        $field->add_error($c->loc("Must not contain spaces"));
    }

    if(my $c_check = $pw->{musthave_lowercase}) {
        my $count = 0;
        map { $_ =~ /^[a-z]$/ and $count++ } split(//, $pass);
        if ($count < $c_check) {
            $field->add_error($c->loc("Must contain at least $c_check lower-case characters"));
        }
    }
    if(my $c_check = $pw->{musthave_uppercase}) {
        my $count = 0;
        map { $_ =~ /^[A-Z]$/ and $count++ } split(//, $pass);
        if ($count < $c_check) {
            $field->add_error($c->loc("Must contain at least $c_check upper-case characters"));
        }
    }
    if(my $c_check = $pw->{musthave_digit}) {
        my $count = 0;
        map { $_ =~ /^[0-9]$/ and $count++ } split(//, $pass);
        if ($count < $c_check) {
            $field->add_error($c->loc("Must contain at least $c_check digits"));
        }
    }
    if(my $c_check = $pw->{musthave_specialchar}) {
        my $count = 0;
        map { $_ =~ /^[^0-9a-zA-Z]$/ and $count++ } split(//, $pass);
        if ($count < $c_check) {
            $field->add_error($c->loc("Must contain at least $c_check special characters"));
        }
    }
    if (!$utf8 && $pass && !NGCP::Panel::Utils::Auth::check_password($pass)) {
        $field->add_error($c->loc('Contains invalid characters'));
    }
    my $res = password_strength($pass);
    if ($res->{score} < 3) {
        $field->add_error($c->loc('Password is too weak'));
    }

    my $lp_rs;
    my $check_last_passwords = 0;
    if ($is_sip_password) {
        my $user;
        my $prov_sub = $c->stash->{subscriber}
                        ? $c->stash->{subscriber}->provisioning_voip_subscriber
                        : undef;
        if ($pass_change && !$prov_sub) {
            $prov_sub = $c->user->provisioning_voip_subscriber;
        }
        if ($field->form->field('username')) {
            $user = $field->form->field('username')->value;
        } elsif ($prov_sub) {
            $user = $prov_sub->username;
        }
        if (defined $user && $pass =~ /$user/i) {
            $field->add_error($c->loc('Must not contain username'));
        }
        if ($pass && $prov_sub && $pass ne $prov_sub->password) {
            $lp_rs = $prov_sub->last_passwords;
            $check_last_passwords = 1;
        }
    } elsif ($is_web_password) {
        my $user;
        my $prov_sub = $c->stash->{subscriber}
                ? $c->stash->{subscriber}->provisioning_voip_subscriber
                : undef;
        if ($pass_change && !$prov_sub) {
            if ($c->user->can('webpassword')) {
                $prov_sub = $c->user;
            }
        }
        if ($field->form->field('webusername')) {
            $user = $field->form->field('webusername')->value;
        } elsif ($prov_sub) {
            $user = $prov_sub->webusername;
        }
        if(defined $user && $pass =~ /$user/i) {
            $field->add_error($c->loc('Must not contain username'));
        }
        if ($pass && $prov_sub) {
            $lp_rs = $prov_sub->last_webpasswords;
            $check_last_passwords = 1;
        }
    } elsif ($is_admin_password) {
        my $user;
        my $admin = $c->stash->{administrator} // undef;
        if ($pass_change && !$admin) {
            $admin = $c->user;
        }
        if ($field->form->field('login')) {
            $user = $field->form->field('login')->value;
        } elsif($admin) {
            $user = $admin->login;
        }
        if (defined $user && $pass =~ /$user/i) {
            $field->add_error($c->loc('Must not contain login'));
        }
        if ($pass && $admin) {
            $lp_rs = $admin->last_passwords;
            $check_last_passwords = 1;
        }
    }
    if ($check_last_passwords) {
        my $bcrypt_cost = 6;
        foreach my $row ($lp_rs->all) {
            my $last_password = $row->value;
            my $enc_pass = $NGCP::Panel::Utils::Auth::ENCRYPT_SUBSCRIBER_WEBPASSWORDS
                                ? NGCP::Panel::Utils::Auth::get_usr_salted_pass($last_password, $pass, $bcrypt_cost)
                                : $pass;
            if ($last_password eq $enc_pass) {
                $field->add_error($c->loc('Password was previously used'));
                last;
            }
        }
    }
}

sub validate_entities {
    my (%params) = @_;
    
    #usage:
    #1. last unless validate_entities(c => $c, contracts => 4711, customers => 4712);
    #2. return unless validate_entities(c => $c, resource => $json, contract_id => 'contracts');
    #3. my $entities = {}; last unless validate_entities(c => $c, resource => $json,
    #    parent_id => $c->user->reseller_id,
    #    profile_id => { resultset => 'billing_profiles',
    #                    optional => 1,
    #                    parent_field => 'reseller_id',
    #                    deleted_field => 'status',
    #                    deleted_value => 'terminated'},
    #    entities => $entities);
    #4. last unless validate_entities(..., error => sub { $self->error($c, HTTP_UNPROCESSABLE_ENTITY, shift); });
    #5. validation in form handlers...
    
    my @args = qw/c resource parent_id error entities/;
    my ($c,$res,$parent_id,$err_code,$entities) = delete @params{@args};    

    my $schema = $c->model('DB');
    if ('CODE' ne ref $err_code) {
        $err_code = sub { return; };
    }
    
    my $validate_field = sub {
        my $name = shift;
        my $field = $params{$name}; # { id, resultset, optional, deleted_field, deleted_value, parent_field }
        my $is_res = 'HASH' eq ref $res;
        if ('HASH' ne ref $field) {
            $field = { ($is_res ? 'resultset' : 'id'), $field };
        }
        if ($is_res) {
            $field->{id} = $res->{$name} if !exists $field->{id};
            $field->{label} //= "'" . $name . "'";
        } else {
            $field->{resultset} //= $name;
            $field->{label} //= $name . ' ID';
        }
        $field->{optional} //= 0;
        $field->{hfh_field} //= $name;
        if (!$field->{optional} && !defined $field->{id}) {
            return 0 unless &{$err_code}("Invalid $field->{label}, not defined.",$field->{hfh_field});
        }
        my $entity = undef;
        eval {
            $entity = (defined $field->{id} ? $schema->resultset($field->{resultset})->find({id => $field->{id}},($field->{lock} ? {for => 'update'} : undef)) : undef);
        };
        if ($@) {
            return 0 unless &{$err_code}($@,$field->{hfh_field});
        }
        if (defined $field->{id} && !defined $entity) {
            return 0 unless &{$err_code}("Invalid $field->{label} ($field->{id}).",$field->{hfh_field});
        }
        if (defined $field->{deleted_field} && defined $field->{deleted_value} && defined $entity) {
            my $deleted_accessor = $field->{deleted_field};
            if (defined $entity->$deleted_accessor() && $field->{deleted_value} eq $entity->$deleted_accessor()) {
                return 0 unless &{$err_code}("Invalid $field->{label} ($field->{id}), $field->{deleted_value}.",$field->{hfh_field});
            }
        }
        if (defined $parent_id && defined $field->{parent_field} && defined $entity) {
            my $parent_accessor = $field->{parent_field};
            if (defined $entity->$parent_accessor() && $parent_id != $entity->$parent_accessor()) {
                return 0 unless &{$err_code}("The $field->{parent} of $field->{label} (" . $entity->$parent_accessor() . ") doesn't match.",$field->{hfh_field});
            }
        }
        if ('HASH' eq ref $entities) {
            $entities->{$name} = $entity;
        }
        return 1;
    };

    foreach my $name (keys %params) {
        return 0 unless &$validate_field($name);
    }  
    
    return 1;
}

sub validate_number_uri {
    my %params = @_;
    my $c = $params{c};
    my $field = $params{field};
    my $val = $field->value;

    unless ($val =~ /^\s*(sip:)*[[:lower:][:upper:][:digit:]+=,;_.~'()!*-]+\@[\w\d\-_\.]+\s*$/ ||
            $val =~ /^\s*\+*[0-9a-z]+\s*$/i) {
        $field->add_error($c->loc('Must be either a number or user@domain format.'));
    }
}

1;

# vim: set tabstop=4 expandtab:
