package NGCP::Panel::Utils::Preferences;

use Sipwise::Base;

use Data::Validate::IP qw/is_ipv4 is_ipv6/;
use NGCP::Panel::Form::Preferences;
use NGCP::Panel::Utils::Generic qw(:all);
use NGCP::Panel::Utils::I18N qw//;
use NGCP::Panel::Utils::Sems;
use JSON qw();
use HTTP::Status qw(:constants);
use File::Type;
use Readonly;
use MIME::Base64 qw(decode_base64);
use List::Util qw(any first);

use constant _DYNAMIC_PREFERENCE_PREFIX => '__';

our $TYPE_PREF_MAP = {
    'domains'           => 'dom',
    'profiles'          => 'prof',
    'subscribers'       => 'usr',
    'peerings'          => 'peer',
    'resellers'         => 'reseller',
    'pbxdevicemodels'   => 'dev',
    'pbxdeviceprofiles' => 'devprof',
    'pbxdevices'        => 'fielddev',
    'contracts'         => 'contract',
};

my $API_TRANSFORM_OUT;
my $API_TRANSFORM_IN;
my $CODE_SUFFIX_FNAME = '_code';
Readonly my $blob_short_value_size => 4096;

sub validate_ipnet {
    my ($field) = @_;
    if ( !$field->value ) {
        $field->add_error("Invalid IPv4 or IPv6 address, must be valid address with optional /net suffix.");
        return;
    }
    return _validate_ipnet(undef, $field->value, sub {
        my ($code, $msg) = @_;
        $field->add_error($msg);
    });
}

sub _validate_ipnet {
    my ($pref, $ipnet, $err_code) = @_;
    if (!defined $err_code || ref $err_code ne 'CODE') {
        $err_code = sub { };
    }
    my ($ip, $net) = split /\//, $ipnet;
    if(is_ipv4($ip)) {
        return 1 unless(defined $net);
        unless(is_int($net) && $net >= 0 && $net <= 32) {
            &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Invalid IPv4 network portion in $pref entry '$ipnet', must be 0 <= net <= 32") if $pref;
            &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Invalid IPv4 network portion, must be 0 <= net <= 32") unless $pref;
            return;
        }
    } elsif(is_ipv6($ip)) {
        return 1 unless(defined $net);
        unless(is_int($net) && $net >= 0 && $net <= 128) {
            &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Invalid IPv6 network portion in $pref entry '$ipnet', must be 0 <= net <= 128") if $pref;
            &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Invalid IPv6 network portion, must be 0 <= net <= 128") unless $pref;
            return;
        }
    } else {
        &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Invalid IPv4 or IPv6 address in $pref entry '$ipnet', must be valid address with optional /net suffix") if $pref;
        &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Invalid IPv4 or IPv6 address, must be valid address with optional /net suffix") unless $pref;
        return;
    }
    return 1;
}

sub prepare_resource {

    my %params = @_;

    my ($c,
        $schema,
        $item,
        $type) = @params{qw/
        c
        schema
        item
        type
    /};

    my $prefs;
    my $blob_rs;
    my %profile_allowed_attrs; # for filtering subscriber attrs on its profile
    my $has_profile = 0;
    my $attr = 0;

    if ($type eq "active") {
        my $sub_prefs = $item->provisioning_voip_subscriber->voip_usr_preferences->search(undef,{
            columns => [ 'value', 'attribute_id'],
        });
        my $profile = $item->provisioning_voip_subscriber->voip_subscriber_profile;
        if ($profile) {
            $has_profile = 1;
            %profile_allowed_attrs = map { $_ => 1 } $profile->profile_attributes->get_column('attribute_id')->all;
        }
        my $dom_prefs = $item->domain->provisioning_voip_domain->voip_dom_preferences->search(undef,{
            columns => ['value', 'attribute_id'],
            result_class => $sub_prefs->result_class,
        });
        #search for location if IP is provided
        my $location_id;
        if ($c->request->param('location_ip')) {
            my $location = $schema->resultset('voip_contract_locations')->search({
                'voip_contract_location_blocks.ip' => $c->request->param('location_ip')
            },{
                join => 'voip_contract_location_blocks'
            })->first;
            $location_id = $location->id if ($location);
        }
        my $ct_prefs = $item->contract->voip_contract_preferences->search({
            location_id => $location_id || undef
        },{
            columns => ['value', 'attribute_id'],
            result_class => $sub_prefs->result_class,
        });

        $prefs = $sub_prefs->union($ct_prefs->search({attribute_id => {-not_in => [map {$_->get_column('attribute_id')} $sub_prefs->all]}}));
        $prefs = $prefs->union($dom_prefs->search({attribute_id => {-not_in => [map {$_->get_column('attribute_id')} $prefs->all]}}));

        $prefs = $prefs->search({
        }, {
            prefetch => 'attribute',
        });

    } else {
        if($type eq "subscribers") {
            $prefs = $item->provisioning_voip_subscriber->voip_usr_preferences;
            $blob_rs = $c->model('DB')->resultset('voip_usr_preferences_blob');
            my $profile = $item->provisioning_voip_subscriber->voip_subscriber_profile;
            if ($profile) {
                $has_profile = 1;
                %profile_allowed_attrs = map { $_ => 1 } $profile->profile_attributes->get_column('attribute_id')->all;
            }
        } elsif($type eq "profiles") {
            $prefs = $item->voip_prof_preferences;
        } elsif($type eq "domains") {
            $prefs = $item->provisioning_voip_domain->voip_dom_preferences;
            $blob_rs = $c->model('DB')->resultset('voip_dom_preferences_blob');
        } elsif($type eq "peerings") {
            $prefs = $item->voip_peer_preferences;
            $blob_rs = $c->model('DB')->resultset('voip_peer_preferences_blob');
        } elsif($type eq "resellers") {
            $prefs = $item->reseller_preferences;
        } elsif($type eq "contracts") {
            $prefs = $item->voip_contract_preferences->search(
                        { location_id => $c->request->param('location_id') || undef },
                        undef);
            $blob_rs = $c->model('DB')->resultset('voip_contract_preferences_blob');
        } elsif($type eq "pbxdevicemodels") {
            $prefs = $item->voip_dev_preferences;
        } elsif($type eq "pbxdeviceprofiles") {
            $prefs = $item->voip_devprof_preferences;
        } elsif($type eq "pbxdevices") {
            $prefs = $item->voip_fielddev_preferences;
        }
        $prefs = $prefs->search({
        }, {
            prefetch => 'attribute',
            order_by => { '-asc' => 'me.id' },
        });
    }

    my $resource;
    foreach my $pref($prefs->all) {
        my $value;
        my $processed = 0;

        if ($c->user->roles eq 'subscriberadmin' || $c->user->roles eq 'subscriber') {
            my $attrname = $pref->attribute->attribute;
            if ($c->user->roles eq 'subscriberadmin' && ! $pref->attribute->expose_to_customer ) {
                next;
            }

            if ($c->user->roles eq 'subscriber' && ! $pref->attribute->expose_to_subscriber ) {
                next;
            }

            if ($has_profile && !$profile_allowed_attrs{$pref->attribute_id}) {
                next;
            }
        }

        SWITCH: for ($pref->attribute->attribute) {
            /^rewrite_calle[re]_(in|out)_dpid$/ && do {
                if(exists $resource->{rewrite_rule_set}) {
                    $processed = 1;
                    last SWITCH;
                }
                my $col = $pref->attribute->attribute;
                $col =~ s/^rewrite_//;
                my $rwr_set = $schema->resultset('voip_rewrite_rule_sets')->find({
                    $col => $pref->value,
                });
                if($rwr_set) {
                    $resource->{rewrite_rule_set} = $rwr_set->name;
                } else {
                    $c->log->error("no rewrite rule set for '".$pref->attribute->attribute."' with value '".$pref->value."' found, although it's stored in preference id ".$pref->id);
                    # let it slip through
                }
                $processed = 1;
                last SWITCH;
            };
            /^cdr_export_sclidui_rwrs_id$/ && do {
                my $pref_name = $pref->attribute->attribute;
                $pref_name =~ s/_id$//;
                my $rwr_set = $schema->resultset('voip_rewrite_rule_sets')->find({
                    id => $pref->value,
                });
                if($rwr_set) {
                    $resource->{$pref_name} = $rwr_set->name;
                } else {
                    $c->log->error("no rewrite rule set for '".$pref->attribute->attribute."' with value '".$pref->value."' found, although it's stored in preference id ".$pref->id);
                    # let it slip through
                }
                $processed = 1;
                last SWITCH;
            };
            /^(adm_)?(cf_)?ncos_id$/ && do {
                my $pref_name = $pref->attribute->attribute;
                $pref_name =~ s/_id$//;

                my $ncos = $schema->resultset('ncos_levels')->find({
                    id => $pref->value,
                });
                if($ncos) {
                    $resource->{$pref_name} = $ncos->level;
                } else {
                    $c->log->error("no ncos level for '".$pref->attribute->attribute."' with value '".$pref->value."' found, although it's stored in preference id ".$pref->id);
                    # let it slip through
                }
                $processed = 1;
                last SWITCH;
            };
            /^(adm_)?(cf_)?ncos_set_id$/ && do {
                my $pref_name = $pref->attribute->attribute;
                $pref_name =~ s/_id$//;

                my $ncos = $schema->resultset('ncos_sets')->find({
                    id => $pref->value,
                });
                if($ncos) {
                    $resource->{$pref_name} = $ncos->name;
                } else {
                    $c->log->error("no ncos set for '".$pref->attribute->attribute."' with value '".$pref->value."' found, although it's stored in preference id ".$pref->id);
                    # let it slip through
                }
                $processed = 1;
                last SWITCH;
            };
            /^emergency_mapping_container_id$/ && do {
                my $pref_name = $pref->attribute->attribute;
                $pref_name =~ s/_id$//;

                my $container = $schema->resultset('emergency_containers')->find({
                    id => $pref->value,
                });
                if($container) {
                    $resource->{$pref_name} = $container->name;
                } else {
                    $c->log->error("no emergency mapping container for '".$pref->attribute->attribute."' with value '".$pref->value."' found, although it's stored in preference id ".$pref->id);
                    # let it slip through
                }
                $processed = 1;
                last SWITCH;
            };
            /^(contract_)?sound_set$/ && do {
                my $set = $schema->resultset('voip_sound_sets')->find({
                    id => $pref->value,
                });
                if($set) {
                    $resource->{$pref->attribute->attribute} = $set->name;
                } else {
                    $c->log->error("no sound set for '".$pref->attribute->attribute."' with value '".$pref->value."' found, although it's stored in preference id ".$pref->id);
                    # let it slip through
                }
                $processed = 1;
                last SWITCH;
            };
            /^(man_)?allowed_ips_grp$/ && do {
                my $pref_name = $pref->attribute->attribute;
                $pref_name =~ s/_grp$//;

                my $sets = $schema->resultset('voip_allowed_ip_groups')->search({
                    group_id => $pref->value,
                }, {
                    order_by => { -asc => 'id' },
                });
                foreach my $set($sets->all) {
                    $resource->{$pref_name} = []
                        unless exists($resource->{$pref_name});
                    push @{ $resource->{$pref_name} }, $set->ipnet;
                }
                $processed = 1;
                last SWITCH;
            };
            /^header_rule_set$/ && do {
                my $hrs = $schema->resultset('voip_header_rule_sets')->find({
                    id => $pref->value,
                });
                if($hrs) {
                    $resource->{$pref->attribute->attribute} = $hrs->name;
                } else {
                    $c->log->error("no header rule set for '".$pref->attribute->attribute."' with value '".$pref->value."' found, although it's stored in preference id ".$pref->id);
                    # let it slip through
                }
                $processed = 1;
                last SWITCH;
            };
            # default
            if($pref->attribute->internal != 0) {
                $processed = 1;
                last SWITCH;
            }
        } # SWITCH
        next if $processed;

        SWITCH: for ($pref->attribute->data_type) {
            /^int$/ && do {
                $value = int($pref->value) if(is_int($pref->value));
                last SWITCH;
            };
            /^boolean$/ && do {
                if (defined $pref->value) {
                    $value = ($pref->value ? JSON::true : JSON::false);
                }
                last SWITCH;
            };
            /^blob$/ && do {
                if (defined $pref->value) {
                    my $blob = $blob_rs->search({ preference_id => $pref->id });
                    my $attribute = $pref->attribute->attribute;
                    if ($c->req->param('preference') && $c->req->param('preference') eq $attribute) {
                        my $data = $blob->first->value;
                        my $ft = File::Type->new();
                        $c->response->header('Content-Disposition' => 'attachment; filename="' . $blob->first->id . '-' . $attribute . '"');
                        $c->response->content_type($ft->mime_type($blob->first->value) || $blob->first->content_type);
                        $c->response->body($data);
                        $c->detach();
                        return 1;
                    }
                    $value = {
                        content_type => $blob->first->content_type,
                        data => $blob->first->value ? '#blob' : undef
                    };
                }
                last SWITCH;
            };
            # default
            $value = $pref->value;
        } # SWITCH
        eval {
            $value = _api_transform_out($c, $pref->attribute, $value);
        };
        if ($@) {
            $c->log->error("Failed to transform pref value - $@");
            # let it slip through
        }
        if($pref->attribute->max_occur != 1) {
            $resource->{$pref->attribute->attribute} = []
                unless(exists $resource->{$pref->attribute->attribute});
            push @{ $resource->{$pref->attribute->attribute} }, $value;
        } else {
            $resource->{$pref->attribute->attribute} = $value;
        }
    }

    $resource = { map { _rename_transform_out($c,$_) => $resource->{$_}; } keys %$resource };

    if($type eq "domains") {
        $resource->{domain_id} = int($item->id);
        $resource->{id} = int($item->id);
    } elsif($type eq "subscribers") {
        $resource->{subscriber_id} = int($item->id);
        $resource->{id} = int($item->id);
    } elsif($type eq "profiles") {
        $resource->{profile_id} = int($item->id);
        $resource->{id} = int($item->id);
    } elsif($type eq "peerings") {
        $resource->{peering_id} = int($item->id);
        $resource->{id} = int($item->id);
    } elsif($type eq "resellers") {
        $resource->{reseller_id} = int($item->id);
        $resource->{id} = int($item->id);
    } elsif($type eq "pbxdevicemodels") {
        $resource->{device_id} = int($item->id);
        $resource->{id} = int($item->id);
    } elsif($type eq "pbxdeviceprofiles") {
        $resource->{profile_id} = int($item->id);
        $resource->{id} = int($item->id);
    } elsif($type eq "pbxdevices") {
        $resource->{device_id} = int($item->id);
        $resource->{id} = int($item->id);
    } elsif($type eq "contracts") {
        $resource->{customer_id} = int($item->id);
        $resource->{id} = int($item->id);
        $prefs->first ? $resource->{location_id} = $prefs->first->location_id
                      : undef;
    }

    return $resource;
}

sub update_preferences {

    my %params = @_;

    my ($c,
        $schema,
        $item,
        $old_resource,
        $resource,
        $type,
        $replace,
        $err_code) = @params{qw/
        c
        schema
        item
        old_resource
        resource
        type
        replace
        err_code
    /};

    if (!defined $err_code || ref $err_code ne 'CODE') {
        $err_code = sub { };
    }

    delete $resource->{id};
    my $accessor;
    my $elem;
    my $pref_type;
    my $reseller_id;
    my $full_rs;
    my $old_auth_prefs = {};

    if($type eq "domains") {
        delete $resource->{domain_id};
        delete $resource->{domainpreferences_id};
        delete $old_resource->{domain_id};
        delete $old_resource->{domainpreferences_id};
        $accessor = $item->domain;
        $elem = $item->provisioning_voip_domain;
        $full_rs = $elem->voip_dom_preferences;
        $pref_type = 'dom_pref';
        $reseller_id = $item->reseller_id;
    } elsif($type eq "profiles") {
        delete $resource->{profile_id};
        delete $resource->{profilepreferences_id};
        delete $old_resource->{profile_id};
        delete $old_resource->{profilepreferences_id};
        $accessor = $item->id;
        $elem = $item;
        $full_rs = $elem->voip_prof_preferences;
        $pref_type = 'prof_pref';
        $reseller_id = $item->profile_set->reseller_id;
    } elsif($type eq "subscribers") {
        delete $resource->{subscriber_id};
        delete $resource->{subscriberpreferences_id};
        delete $old_resource->{subscriber_id};
        delete $old_resource->{subscriberpreferences_id};
        $accessor = $item->username . '@' . $item->domain->domain;
        $elem = $item->provisioning_voip_subscriber;
        $full_rs = $elem->voip_usr_preferences;
        if ($c->user->roles eq 'subscriberadmin' || $c->user->roles eq 'subscriber') {
            $full_rs = $full_rs->search_rs({
                $c->user->roles eq 'subscriberadmin'
                    ? ('attribute.expose_to_customer' => 1)
                    : ('attribute.expose_to_subscriber' => 1),
            },{
                join => 'attribute',
            });

            if ($elem && $elem->voip_subscriber_profile) {
                my @allowed_attr_ids = $elem->voip_subscriber_profile->profile_attributes
                    ->get_column('attribute_id')->all;
                $full_rs = $full_rs->search_rs({
                    'attribute.id' => { '-in' => \@allowed_attr_ids },
                });
            }
        }
        $pref_type = 'usr_pref';
        $reseller_id = $item->contract->contact->reseller_id;
    } elsif($type eq "peerings") {
        delete $resource->{peer_id};
        delete $resource->{peerpreferences_id};
        delete $old_resource->{peer_id};
        delete $old_resource->{peerpreferences_id};
        $accessor = $item->name;
        $elem = $item;
        $full_rs = $elem->voip_peer_preferences;
        $pref_type = 'peer_pref';
        $reseller_id = 1;
    } elsif($type eq "resellers") {
        delete $resource->{reseller_id};
        delete $resource->{resellerpreferences_id};
        delete $old_resource->{reseller_id};
        delete $old_resource->{resellerpreferences_id};
        $accessor = $item->name;
        $elem = $item;
        $full_rs = $elem->reseller_preferences;
        $pref_type = 'reseller_pref';
        $reseller_id = $item->id;
    } elsif($type eq "contracts") {
        delete $resource->{customer_id};
        delete $old_resource->{customer_id};
        delete $resource->{location_id};
        delete $old_resource->{location_id};
        $accessor = $item->id;
        $elem = $item;
        $full_rs = $elem->voip_contract_preferences->search_rs(
                    { location_id => $c->request->param('location_id') || undef },
                    undef);
        $pref_type = 'contract_pref';
        $reseller_id = $item->contact->reseller_id;
    } elsif($type eq "pbxdevicemodels") {
        delete $resource->{device_id};
        delete $old_resource->{device_id};
        delete $resource->{pbxdevicepreferences_id};
        delete $old_resource->{pbxdevicepreferences_id};
        $accessor = $item->id;
        $elem = $item;
        $full_rs = $elem->voip_dev_preferences->search_rs();
        $pref_type = 'dev_pref';
        $reseller_id = $item->reseller_id;
    } elsif($type eq "pbxdeviceprofiles") {
        delete $resource->{profile_id};
        delete $old_resource->{profile_id};
        delete $resource->{pbxdeviceprofilepreferences_id};
        delete $old_resource->{pbxdeviceprofilepreferences_id};
        $accessor = $item->id;
        $elem = $item;
        $full_rs = $elem->voip_devprof_preferences->search_rs();
        $pref_type = 'devprof_pref';
        $reseller_id = $item->config->device->reseller_id;
    } elsif($type eq "pbxdevices") {
        delete $resource->{device_id};
        delete $old_resource->{device_id};
        delete $resource->{pbxfielddevicepreferences_id};
        delete $old_resource->{pbxfielddevicepreferences_id};
        $accessor = $item->id;
        $elem = $item;
        $full_rs = $elem->voip_fielddev_preferences->search_rs();
        $pref_type = 'fielddev_pref';
        $reseller_id = $item->profile->config->device->reseller_id;
    } else {
        return;
    }

    if (($type eq 'subscribers' || $type eq 'peerings')
            && grep {/^peer_auth_/} keys %{ $resource }) {
        get_peer_auth_params($c, $elem, $type, $old_auth_prefs);
    };

    # make sure to not clear any internal prefs, except for those defined
    # in extra:
    my $extra = [qw/
        rewrite_caller_in_dpid rewrite_caller_out_dpid
        rewrite_callee_in_dpid rewrite_callee_out_dpid
        rewrite_caller_lnp_dpid rewrite_callee_lnp_dpid
        cdr_export_sclidui_rwrs_id
        ncos_id adm_ncos_id adm_cf_ncos_id
        ncos_set_id adm_ncos_set_id adm_cf_ncos_set_id
        emergency_mapping_container_id
        sound_set contract_sound_set
        allowed_ips_grp man_allowed_ips_grp
        header_rule_set
    /];
    $full_rs = $full_rs->search({
        -or => [
            'attribute.internal' => 0,
            'attribute.attribute' => { 'in' => $extra },
        ]
    },{
        join => 'attribute',
    });

    $resource = { map { _rename_transform_in($c,$_) => $resource->{$_}; } keys %$resource };
    if($replace) {
        # in case of PUT, we remove all old entries
        try {
            $full_rs->delete_all;
        } catch($e) {
            $c->log->error("failed to clear preferences for '$accessor': $e");
            &$err_code(HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error.", $e);
            return;
        };
    } else {
        $old_resource = { map { _rename_transform_in($c,$_) => $old_resource->{$_}; } keys %$old_resource };
        # in case of PATCH, we remove only those entries marked for removal in the patch
        try {
            foreach my $k(keys %{ $old_resource }) {
                SWITCH: for ($k) {
                    /^cli$/ && do {
                        if ($c->user->roles eq "subscriberadmin" || $c->user->roles eq "subscriber") {
                            my $cli = $resource->{$k};
                            my @allowed_cli_numbers = $c->model('DB')->resultset('voip_dbaliases')->search({
                                'subscriber.account_id' => $item->contract_id,
                            },{
                                select => ['me.username'],
                                as => ['number'],
                                join => 'subscriber',
                                result_class => 'DBIx::Class::ResultClass::HashRefInflator',
                            })->all;
                            unless (any { $_->{number} eq $cli } @allowed_cli_numbers) {
                                my $err_msg = "Only numbers that belong to the customer can be assigned as 'cli'";
                                $c->log->error($err_msg);
                                &$err_code(HTTP_UNPROCESSABLE_ENTITY, $err_msg);
                                return;
                            }
                        } else {
                            unless (exists $resource->{$k}) {
                                my $rs = get_preference_rs($c, $TYPE_PREF_MAP->{$type}, $elem, $k);
                                last SWITCH unless $rs; # unknown resource, just ignore
                                $rs->delete;
                            }
                        }
                        last SWITCH;
                    };
                    # no special treatment for *_sound_set deletion, as id is stored in right name
                    /^rewrite_rule_set$/ && do {
                        unless(exists $resource->{$k}) {
                            foreach my $p(qw/
                                caller_in_dpid callee_in_dpid
                                caller_out_dpid callee_out_dpid
                                caller_lnp_dpid callee_lnp_dpid/) {
                                my $rs = get_preference_rs($c, $TYPE_PREF_MAP->{$type}, $elem, 'rewrite_' . $p);
                                next unless $rs; # unknown resource, just ignore
                                $rs->delete;
                            }
                        }
                        last SWITCH;
                    };
                    /^cdr_export_sclidui_rwrs/ && do {
                        unless(exists $resource->{$k}) {
                            my $rs = get_preference_rs($c, $TYPE_PREF_MAP->{$type}, $elem, $k . '_id');
                            last SWITCH unless $rs; # unknown resource, just ignore
                            $rs->delete;
                        }
                        last SWITCH;
                    };
                    /^(adm_)?(cf_)?ncos(_set)?$/ && do {
                        unless(exists $resource->{$k}) {
                            my $rs = get_preference_rs($c, $TYPE_PREF_MAP->{$type}, $elem, $k . '_id');
                            last SWITCH unless $rs; # unknown resource, just ignore
                            $rs->delete;
                        }
                        last SWITCH;
                    };
                    /^emergency_mapping_container$/ && do {
                        unless(exists $resource->{$k}) {
                            my $rs = get_preference_rs($c, $TYPE_PREF_MAP->{$type}, $elem, $k . '_id');
                            last SWITCH unless $rs; # unknown resource, just ignore
                            $rs->delete;
                        }
                        last SWITCH;
                    };
                    /^(man_)?allowed_ips$/ && do {
                        unless(exists $resource->{$k}) {
                            my $rs = get_preference_rs($c, $TYPE_PREF_MAP->{$type}, $elem, $k . '_grp');
                            last SWITCH unless $rs; # unknown resource, just ignore
                            if($rs->first) {
                                $c->model('DB')->resultset('voip_allowed_ip_groups')->search({
                                    group_id => $rs->first->value,
                                })->delete;
                            }
                            $rs->delete;
                        }
                        last SWITCH;
                    };
                    # default
                    unless(exists $resource->{$k}) {
                        my $rs = get_preference_rs($c, $TYPE_PREF_MAP->{$type}, $elem, $k);
                        last SWITCH unless $rs; # unknown resource, just ignore
                        $rs->delete;
                        if ($type eq "subscribers" && ($k eq 'voicemail_echo_number' || $k eq 'cli')) {
                            NGCP::Panel::Utils::Subscriber::update_voicemail_number(
                                c => $c,
                                subscriber => $item,
                            );
                        }
                    }
                } # SWITCH
            }
        } catch($e) {
            $c->log->error("failed to clear preference for '$accessor': $e");
            &$err_code(HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error.", $e);
            return;
        };
    }

    my %nullable = (
        lock => 1,
    );

    foreach my $pref(keys %{ $resource }) {
        next if (not defined $resource->{$pref} and not $nullable{$pref});
        my $pref_rs = get_preference_rs($c, $TYPE_PREF_MAP->{$type}, $elem, $pref);
        unless($pref_rs) {
            $c->log->debug("removing unknown preference '$pref' from update");
            next;
        }
        $pref_rs = $pref_rs->search(undef, {
            order_by => { '-asc' => 'id' },
        });

        # TODO: can't we get this via $pref_rs->search_related or $pref_rs->related_resultset?
        my $meta = $c->model('DB')->resultset('voip_preferences')->find({
            attribute => $pref, $pref_type => 1,
        });
        unless($meta) {
            $c->log->error("failed to get voip_preference entry for '$pref'");
            &$err_code(HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error.");
            return;
        }

        try {
            my $vtype = ref $resource->{$pref};
            my $maxlen = 128;

            if($vtype eq "") {
                if(defined $resource->{$pref} and length($resource->{$pref}) > $maxlen) {
                    $c->log->error("preference '$pref' exceeds maximum length of $maxlen characters");
                    &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Preference '$pref' exceeds maximum length of $maxlen characters");
                    return;
                }
            } elsif($vtype eq "ARRAY") {
                foreach my $a(@{ $resource->{$pref} }) {
                    if(defined $a and length($a) > $maxlen) {
                        $c->log->error("element in preference '$pref' exceeds maximum length of $maxlen characters");
                        &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Element in preference '$pref' exceeds maximum length of $maxlen characters");
                        return;
                    }
                }
            }

            if (($meta->data_type eq "boolean" or _exists_api_transform_in($c, $pref)) and JSON::is_bool($resource->{$pref})) {
                $vtype = "";
            }
            if($meta->max_occur == 1 && $vtype ne "" && $meta->data_type ne "blob") {
                $c->log->error("preference '$pref' has max_occur '".$meta->max_occur."', but value got passed in as '$vtype', expected flat value");
                &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Invalid data type '$vtype' for preference '$pref', expected flat value");
                return;
            } elsif($meta->max_occur != 1 && $vtype ne "ARRAY") {
                $c->log->error("preference '$pref' has max_occur '".$meta->max_occur."', but value got passed in as '$vtype', expected ARRAY");
                &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Invalid data type '$vtype' for preference '$pref', expected ARRAY");
                return;
            }

            SWITCH: for ($pref) {
                /^rewrite_rule_set$/ && do {
                    my $rwr_set = $c->model('DB')->resultset('voip_rewrite_rule_sets')->find({
                        name => $resource->{$pref},
                        $pref_type ne 'peer_pref' ? (reseller_id => $reseller_id) : (),
                    });
                    unless($rwr_set) {
                        $c->log->error("no rewrite rule set '".$resource->{$pref}."' for reseller id $reseller_id found");
                        &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Unknown rewrite_rule_set '".$resource->{$pref}."'");
                        return;
                    }
                    foreach my $k(qw/
                                    caller_in_dpid callee_in_dpid
                                    caller_out_dpid callee_out_dpid
                                    caller_lnp_dpid callee_lnp_dpid/) {
                        my $rs = get_preference_rs($c, $TYPE_PREF_MAP->{$type}, $elem, 'rewrite_'.$k);
                        if($rs->first) {
                            $rs->first->update({ value => $rwr_set->$k });
                        } else {
                            $rs->create({ value => $rwr_set->$k });
                        }
                    }
                    last SWITCH;
                };
                /^cdr_export_sclidui_rwrs$/ && do {
                    my $pref_name = $pref . "_id";
                    my $rwr_set = $c->model('DB')->resultset('voip_rewrite_rule_sets')->find({
                        name => $resource->{$pref},
                        reseller_id => $reseller_id,
                    });
                    unless($rwr_set) {
                        $c->log->error("no rewrite rule set '".$resource->{$pref}."' for reseller id $reseller_id found");
                        &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Unknown rewrite_rule_set '".$resource->{$pref}."'");
                        return;
                    }
                    my $rs = get_preference_rs($c, $TYPE_PREF_MAP->{$type}, $elem, $pref_name);
                    if($rs->first) {
                        $rs->first->update({ value => $rwr_set->id });
                    } else {
                        $rs->create({ value => $rwr_set->id });
                    }
                    last SWITCH;
                };
                /^header_rule_set$/ && do {
                    my $hdr_set = $c->model('DB')->resultset('voip_header_rule_sets')->find({
                        name => $resource->{$pref},
                        $pref_type ne 'peer_pref' ? (reseller_id => $reseller_id) : (),
                    });
                    unless ($hdr_set) {
                        $c->log->error("no header rule set '".$resource->{$pref}."' for reseller id $reseller_id found");
                        &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Unknown header_rule_set '".$resource->{$pref}."'");
                        return;
                    }
                    my $rs = get_preference_rs($c, $TYPE_PREF_MAP->{$type}, $elem, $pref);
                    if ($rs->first) {
                        $rs->first->update({ value => $hdr_set->id });
                    } else {
                        $rs->create({ value => $hdr_set->id });
                    }

                    last SWITCH;
                };
                /^(adm_)?(cf_)?ncos$/ && do {
                    my $pref_name = $pref . "_id";
                    my $ncos = $c->model('DB')->resultset('ncos_levels')->find({
                        level => $resource->{$pref},
                        reseller_id => $reseller_id,
                    });
                    unless($ncos) {
                        $c->log->error("no ncos level '".$resource->{$pref}."' for reseller id $reseller_id found");
                        &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Unknown ncos_level '".$resource->{$pref}."'");
                        return;
                    }
                    my $rs = get_preference_rs($c, $TYPE_PREF_MAP->{$type}, $elem, $pref_name);
                    if($rs->first) {
                        $rs->first->update({ value => $ncos->id });
                    } else {
                        $rs->create({ value => $ncos->id });
                    }
                    last SWITCH;
                };
                /^(adm_)?(cf_)?ncos_set$/ && do {
                    my $pref_name = $pref . "_id";
                    my $ncos = $c->model('DB')->resultset('ncos_sets')->find({
                        name => $resource->{$pref},
                        reseller_id => $reseller_id,
                    });
                    unless($ncos) {
                        $c->log->error("no ncos set '".$resource->{$pref}."' for reseller id $reseller_id found");
                        &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Unknown ncos_set '".$resource->{$pref}."'");
                        return;
                    }
                    my $rs = get_preference_rs($c, $TYPE_PREF_MAP->{$type}, $elem, $pref_name);
                    if($rs->first) {
                        $rs->first->update({ value => $ncos->id });
                    } else {
                        $rs->create({ value => $ncos->id });
                    }
                    last SWITCH;
                };
                /^emergency_mapping_container$/ && do {
                    my $pref_name = $pref . "_id";
                    my $container = $c->model('DB')->resultset('emergency_containers')->find({
                        name => $resource->{$pref},
                        reseller_id => $reseller_id,
                    });
                    unless($container) {
                        $c->log->error("no emergency mapping container '".$resource->{$pref}."' for reseller id $reseller_id found");
                        &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Unknown emergency mapping container '".$resource->{$pref}."'");
                        return;
                    }
                    my $rs = get_preference_rs($c, $TYPE_PREF_MAP->{$type}, $elem, $pref_name);
                    if($rs->first) {
                        $rs->first->update({ value => $container->id });
                    } else {
                        $rs->create({ value => $container->id });
                    }
                    last SWITCH;
                };
                /^(contract_)?sound_set$/ && do {
                    my $is_contract_sound_set = $1 ? 1 : 0;
                    my $set_rs = $c->model('DB')->resultset('voip_sound_sets')->search({
                        name => $resource->{$pref},
                    });
                    if ($pref_type ne 'peer_pref') {
                        $set_rs = $set_rs->search({
                            reseller_id => $reseller_id,
                        });
                    }
                    if ($pref_type eq 'usr_pref' && $c->user->roles eq 'subscriberadmin') {
                        my $contract_id = $elem->contract->id;
                        if ($is_contract_sound_set) {
                            $set_rs = $set_rs->search({
                                reseller_id => $reseller_id,
                                contract_id => $contract_id,
                            });
                        } else {
                            $set_rs = $set_rs->search({
                                'contract_id' => undef,
                                'reseller_id' => $reseller_id,
                                'expose_to_customer' => 1,
                            });
                        }
                    }

                    my $set = $set_rs->first;
                    unless($set) {
                        $c->log->error("no $pref '".$resource->{$pref}."' for reseller id $reseller_id found");
                        &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Unknown $pref '".$resource->{$pref}."'");
                        return;
                    }
                    my $rs = get_preference_rs($c, $TYPE_PREF_MAP->{$type}, $elem, $pref);
                    if($rs->first) {
                        $rs->first->update({ value => $set->id });
                    } else {
                        $rs->create({ value => $set->id });
                    }
                    last SWITCH;
                };
                /^(man_)?allowed_ips$/ && do {
                    my $pref_name = $pref . "_grp";
                    my $aig_rs;
                    my $aig_group_id;
                    my $rs = get_preference_rs($c, $TYPE_PREF_MAP->{$type}, $elem, $pref_name);
                    if($rs->first) {
                        $aig_group_id = $rs->first->value;
                        $aig_rs = $c->model('DB')->resultset('voip_allowed_ip_groups')->search({
                            group_id => $aig_group_id
                        });
                        $aig_rs->delete;
                    } else {
                        my $new_group = $c->model('DB')->resultset('voip_aig_sequence')->create({});
                        $aig_group_id = $new_group->id;
                        $aig_rs = $c->model('DB')->resultset('voip_allowed_ip_groups')->search({
                            group_id => $aig_group_id
                        });
                        $c->model('DB')->resultset('voip_aig_sequence')->search_rs({
                                id => { '<' => $aig_group_id },
                            })->delete_all;
                    }
                    foreach my $ip(@{ $resource->{$pref} }) {
                        unless(_validate_ipnet($pref, $ip, $err_code)) {
                            $c->log->error("invalid $pref entry '$ip'");
                            return;
                        }
                        $aig_rs->create({ ipnet => $ip });
                    }
                    unless($rs->first) {
                        $rs->create({ value => $aig_group_id });
                    }
                    # in contrast to panel, it does not drop the allowed_ips_grp pref, if empty ipnets.
                    last SWITCH;
                };
                /^lock$/ && do {
                    my $v = $resource->{$pref};
                    return unless _check_pref_value($c, $meta, $v, $pref_type, $err_code);
                    NGCP::Panel::Utils::Subscriber::lock_provisoning_voip_subscriber(
                        c => $c,
                        prov_subscriber => $elem,
                        level => $v, # || 0
                    );
                    last SWITCH;
                };
                /^allowed_clis$/ && do {
                    my $old_seen = {};
                    unless ($replace) {
                        #in case of PATCH, check duplicates only for new values, since there could already be duplicates in some systems
                        ($old_seen) = array_to_map($old_resource->{$pref},undef,undef,'first');
                    }
                    my $seen = {};
                    foreach my $allowed_cli (@{$resource->{$pref} // []}) {
                        next if exists $old_seen->{$allowed_cli};
                        if (exists $seen->{$allowed_cli}) {
                            $c->log->error("Duplicate $pref value: ".$allowed_cli);
                            &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Duplicate $pref value: ".$allowed_cli);
                            return;
                        } else {
                            $seen->{$allowed_cli} = 1;
                        }
                    }
                };
                if ($meta->data_type eq 'blob') {
                    if ($resource->{$pref}->{data} ne '#blob'){
                        my $file = decode_base64($resource->{$pref}->{data});
                        my $rs = get_preference_rs($c, $TYPE_PREF_MAP->{$type}, $elem, $pref);
                        my $blob_rs = $c->model('DB')->resultset("voip_$TYPE_PREF_MAP->{$type}_preferences_blob");
                        if ($rs->first) {
                            my $blob = $blob_rs->search({ preference_id => $rs->first->id });
                            if ($blob->first) {
                                $blob->update({
                                    preference_id => $rs->first->id,
                                    $file ? (value => $file) : (),
                                    $resource->{$pref}->{content_type} ? (content_type => $resource->{$pref}->{content_type}) : (),
                                });
                            } else {
                                $blob_rs->create({
                                    preference_id => $rs->first->id,
                                    value => ($file ? $file : undef),
                                    content_type => $resource->{$pref}->{content_type},
                                });
                            }
                        } else {
                            $rs->create({ value => 0 });
                            $blob_rs->create({
                                preference_id => $rs->first->id,
                                value => ($file ? $file : undef),
                                content_type => $resource->{$pref}->{content_type},
                            });
                        }
                    }
                } elsif($meta->max_occur != 1) { #default
                    $pref_rs->delete;
                    foreach my $v(@{ $resource->{$pref} }) {
                        return unless _check_pref_value($c, $meta, $v, $pref_type, $err_code);
                        eval {
                            $v = _api_transform_in($c, $meta, $v);
                        };
                        if ($@) {
                            $c->log->error("Failed to transform pref value - $@");
                            &$err_code(HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error.", $@); # TODO?
                            return;
                        }
						if(JSON::is_bool($v)){
							$v =  $v ? 1 : 0 ;
						}
                        $pref_rs->create({ value => $v });
                    }
                } elsif($pref_rs->first) {
                    return unless _check_pref_value($c, $meta, $resource->{$pref}, $pref_type, $err_code);
                    eval {
                        $resource->{$pref} = _api_transform_in($c, $meta, $resource->{$pref});
                    };
                    if ($@) {
                        $c->log->error("Failed to transform pref value - $@");
                        &$err_code(HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error.", $@); # TODO?
                        return;
                    }
                    if(JSON::is_bool($resource->{$pref})){
						$resource->{$pref} =  $resource->{$pref} ? 1 : 0 ;
					}
                    $pref_rs->first->update({ value => $resource->{$pref} });
                } else {
                    return unless _check_pref_value($c, $meta, $resource->{$pref}, $pref_type, $err_code);
                    eval {
                        $resource->{$pref} = _api_transform_in($c, $meta, $resource->{$pref});
                    };
                    if ($@) {
                        $c->log->error("Failed to transform pref value - $@");
                        &$err_code(HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error.", $@); # TODO?
                        return;
                    }
                    if(JSON::is_bool($resource->{$pref})){
						$resource->{$pref} =  $resource->{$pref} ? 1 : 0 ;
					}
                    $pref_rs->create({ value => $resource->{$pref} });
                }
            } # SWITCH
            if ($type eq "subscribers" && ($pref eq 'voicemail_echo_number' || $pref eq 'cli')) {
                NGCP::Panel::Utils::Subscriber::update_voicemail_number(
                    c => $c,
                    subscriber => $item,
                );
            }
        } catch($e) {
            $c->log->error("failed to update preference for '$accessor': $e");
            &$err_code(HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error.", $e);
            return;
        }
    }

    if ($type eq 'subscribers' || $type eq 'peerings') {
        if (keys %{ $old_auth_prefs }) {
            my $new_auth_prefs = {};
            my $prov_object = $elem;
            my $sems_type = $type eq 'subscribers' ? 'subscriber' : 'peering';
            get_peer_auth_params($c, $prov_object, $type, $new_auth_prefs);
            if (!compare($old_auth_prefs, $new_auth_prefs)) {
                $c->log->debug("peer_auth_params changed. updating sems registration.");
                try {
                    update_sems_peer_auth(
                        $c, $prov_object, $sems_type,
                        $old_auth_prefs, $new_auth_prefs
                    );
                } catch($e) {
                    $c->log->error("Failed to set peer registration: $e");
                    &$err_code(HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error.", $e); # TODO?
                    return;
                }
            }
        }
    }

    return $item;
}

sub _init_transform {
    my ($transform,$conf) = @_;
    unless (defined $transform) {
        $transform = {};
        if (defined $conf) {
            foreach my $p (keys %$conf) {
                $transform->{$p} = {};
                if ('HASH' eq ref $conf->{$p}) {
                    foreach my $v (keys %{$conf->{$p}}) {
                        if ($v =~ /^([a-z0-9_]+)$CODE_SUFFIX_FNAME$/) {
                            ## no critic (BuiltinFunctions::ProhibitStringyEval)
                            $transform->{$p}->{$1} = eval($conf->{$p}->{$v});
                            die("$p '$v': " . $@) if $@;
                        } else {
                            $transform->{$p}->{$v} = $conf->{$p}->{$v};
                        }
                    }
                } else {
                    $transform->{$p} = $conf->{$p};
                }
            }
        }
    }
    return $transform;
}

sub _exists_api_transform_in {
    my ($c, $pref) = @_;
    if ($c->request and $c->request->path =~/^api\//i) {
        $API_TRANSFORM_IN = _init_transform($API_TRANSFORM_IN,$c->config->{preference_in_transformations});
        if (exists $API_TRANSFORM_IN->{$pref}) {
            return 1;
        }
    }
    return 0;
}

sub _rename_transform_in {
    my ($c, $pref) = @_;
    if ($c->request and $c->request->path =~/^api\//i) {
        $API_TRANSFORM_IN = _init_transform($API_TRANSFORM_IN,$c->config->{preference_in_transformations});
        if (exists $API_TRANSFORM_IN->{$pref} and not ref $API_TRANSFORM_IN->{$pref}) {
            return $API_TRANSFORM_IN->{$pref};
        }
    }
    return $pref;
}

sub _api_transform_in {
    my ($c, $meta, $value) = @_;
    if ($c->request and $c->request->path =~/^api\//i) {
        $API_TRANSFORM_IN = _init_transform($API_TRANSFORM_IN,$c->config->{preference_in_transformations});
        if (exists $API_TRANSFORM_IN->{$meta->attribute}) {
            if (defined $value) {
                my $v = $value;
                if (JSON::is_bool($v)) {
                    $v =  $v ? 1 : 0 ;
                }
                if (exists $API_TRANSFORM_IN->{$meta->attribute}->{$v}) {
                    $value = $API_TRANSFORM_IN->{$meta->attribute}->{$v};
                    if ('CODE' eq ref $value) {
                        eval {
                            $value = $value->($meta,$value);
                        };
                        if ($@) {
                            die($meta->attribute . ": " . $@);
                        }
                    }
                }
            }
        }
    }
    return $value;
}

sub _rename_transform_out {
    my ($c, $pref) = @_;
    if ($c->request and $c->request->path =~/^api\//i) {
        $API_TRANSFORM_OUT = _init_transform($API_TRANSFORM_OUT,$c->config->{preference_out_transformations});
        use Data::Dumper;
        $c->log->debug(Dumper($API_TRANSFORM_OUT));
        if (exists $API_TRANSFORM_OUT->{$pref} and not ref $API_TRANSFORM_OUT->{$pref}) {
            return $API_TRANSFORM_OUT->{$pref};
        }
    }
    return $pref;
}

sub _api_transform_out {
    my ($c, $meta, $value) = @_;
    if ($c->request and $c->request->path =~/^api\//i) {
        $API_TRANSFORM_OUT = _init_transform($API_TRANSFORM_OUT,$c->config->{preference_out_transformations});
        if (exists $API_TRANSFORM_OUT->{$meta->attribute}) {
            if (defined $value) {
                my $v = $value;
                if (JSON::is_bool($v)) {
                    $v =  $v ? 1 : 0 ;
                }
                if (exists $API_TRANSFORM_OUT->{$meta->attribute}->{$v}) {
                    $value = $API_TRANSFORM_OUT->{$meta->attribute}->{$v};
                    if ('CODE' eq ref $value) {
                        eval {
                            $value = $value->($meta,$value);
                        };
                        if ($@) {
                            die($meta->attribute . ": " . $@);
                        }
                    }
                }
            }
        }
    }
    return $value;
}

sub _check_pref_value {
    my ($c, $meta, $value, $pref_type, $err_code) = @_;

    return 1 if _exists_api_transform_in($c,$meta->attribute);

    if (!defined $err_code || ref $err_code ne 'CODE') {
        $err_code = sub { };
    }

    my $err;

    my $vtype = ref $value;
    if (($meta->data_type eq "boolean" and JSON::is_bool($value)) or $meta->data_type eq "blob") {
        $vtype = "";
    }
    unless($vtype eq "") {
        $c->log->error("preference '".$meta->attribute."' has invalid value data structure, expected plain value");
        &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Invalid data structure as value for element in preference '".$meta->attribute."', expected plain value");
        return;
    }

    SWITCH: for ($meta->data_type) {
        /^int$/ && do {
            $err = 1 unless is_int($value);
            last SWITCH;
        };
        /^boolean$/ && do {
            unless (JSON::is_bool($value)
                    or (is_int($value) and ($value == 0 or $value == 1))) {
                $err = 1;
            }
            last SWITCH;
        };
        # default
    } # SWITCH
    if($err) {
        $c->log->error("preference '".$meta->attribute."' has invalid value data type, expected '".$meta->data_type."'");
        &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Invalid data type for element in preference '".$meta->attribute."', expected '".$meta->data_type."'");
        return;
    }

    if($meta->data_type eq "enum") {
        my $enum = $c->model('DB')->resultset('voip_preferences_enum')->find({
            preference_id => $meta->id,
            $pref_type => 1,
            value => $value,
        });
        unless($enum) {
            $c->log->error("preference '".$meta->attribute."' has invalid enum value '".$value."'");
            &$err_code(HTTP_UNPROCESSABLE_ENTITY, "Invalid enum value in preference '".$meta->attribute."'");
            return;
        }
    }

    return 1;
}

sub load_preference_list {
    my %params = @_;

    my $c = $params{c};
    my $pref_values = $params{pref_values};
    my $peer_pref = $params{peer_pref};
    my $reseller_pref = $params{reseller_pref};
    my $dom_pref = $params{dom_pref};
    my $dev_pref = $params{dev_pref};
    my $devprof_pref = $params{devprof_pref};
    my $fielddev_pref = $params{fielddev_pref};
    my $prof_pref = $params{prof_pref};
    my $usr_pref = $params{usr_pref};
    my $contract_pref = $params{contract_pref};
    my $contract_location_pref = $params{contract_location_pref};
    my $profile = $params{sub_profile};

    my $customer_view = $params{customer_view} // 0;
    my $subscriber_view = $params{subscriber_view} // 0;
    my $cloudpbx_enabled = $c->license('pbx') && $c->config->{features}{cloudpbx};

    my $search_conditions = $params{search_conditions};

    my $pref_rs = $c->model('DB')
        ->resultset('voip_preference_groups')
        ->search({ 'voip_preferences.internal' => { '<=' => 0 },
            $contract_pref ? ('voip_preferences.contract_pref' => 1,
                -or => ['voip_preferences_enums.contract_pref' => 1,
                    'voip_preferences_enums.contract_pref' => undef]) : (),
            $contract_location_pref ? ('voip_preferences.contract_location_pref' => 1,
                -or => ['voip_preferences_enums.contract_location_pref' => 1,
                    'voip_preferences_enums.contract_location_pref' => undef]) : (),
            $peer_pref ? ('voip_preferences.peer_pref' => 1,
                -or => ['voip_preferences_enums.peer_pref' => 1,
                    'voip_preferences_enums.peer_pref' => undef]) : (),
            $reseller_pref ? ('voip_preferences.reseller_pref' => 1,
                -or => ['voip_preferences_enums.reseller_pref' => 1,
                    'voip_preferences_enums.reseller_pref' => undef]) : (),
            $dom_pref ? ('voip_preferences.dom_pref' => 1,
                -or => ['voip_preferences_enums.dom_pref' => 1,
                    'voip_preferences_enums.dom_pref' => undef]) : (),
            $dev_pref ? ('voip_preferences.dev_pref' => 1,
                -or => ['voip_preferences_enums.dev_pref' => 1,
                    'voip_preferences_enums.dev_pref' => undef]) : (),
            $devprof_pref ? ('voip_preferences.devprof_pref' => 1,
                -or => ['voip_preferences_enums.devprof_pref' => 1,
                    'voip_preferences_enums.devprof_pref' => undef]) : (),
            $fielddev_pref ? ('voip_preferences.fielddev_pref' => 1,
                -or => ['voip_preferences_enums.fielddev_pref' => 1,
                    'voip_preferences_enums.fielddev_pref' => undef]) : (),
            $prof_pref ? ('voip_preferences.prof_pref' => 1,
                -or => ['voip_preferences_enums.prof_pref' => 1,
                    'voip_preferences_enums.prof_pref' => undef]) : (),
            $usr_pref ? ('voip_preferences.usr_pref' => 1,
                -or => ['voip_preferences_enums.usr_pref' => 1,
                    'voip_preferences_enums.usr_pref' => undef]) : (),
            $customer_view ? ('voip_preferences.expose_to_customer' => 1) : (),
            $subscriber_view ? ('voip_preferences.expose_to_subscriber' => 1) : (),
            $cloudpbx_enabled ? () : ('me.name' => { '!=' => 'Cloud PBX'}),
            }, {
                prefetch => {'voip_preferences' => 'voip_preferences_enums'},
            });
    if($search_conditions) {
        if('ARRAY' eq ref $search_conditions){
            $pref_rs = $pref_rs->search(@$search_conditions);
        }else{
            $pref_rs = $pref_rs->search($search_conditions);
        }
    }
    my @pref_groups = $pref_rs->all;

    foreach my $group(@pref_groups) {
        my @group_prefs = $group->voip_preferences->all;

        foreach my $pref(@group_prefs) {

            my @values = @{
                exists $pref_values->{$pref->attribute}
                    ? $pref_values->{$pref->attribute}
                    : []
            };
            if($pref->attribute eq "rewrite_rule_set") {
                my $tmp;
                $pref->{rwrs_id} = $pref_values->{rewrite_caller_in_dpid} &&
                    ($tmp = $c->stash->{rwr_sets_rs}->search({
                        caller_in_dpid => $pref_values->{rewrite_caller_in_dpid}
                    })->first) ?
                    $tmp->id
                    : undef;
            } elsif($pref->attribute eq "cdr_export_sclidui_rwrs") {
                my $tmp;
                $pref->{rwrs_id} = $pref_values->{$pref->attribute . '_id'} &&
                    ($tmp = $c->stash->{rwr_sets_rs}->search({
                        id => $pref_values->{$pref->attribute . '_id'}
                    })->first) ?
                    $tmp->id
                    : undef;
            } elsif($pref->attribute eq "ncos") {
                if ($pref_values->{ncos_id} &&
                    (my $tmp = $c->stash->{ncos_levels_rs}
                        ->find($pref_values->{ncos_id}) )) {
                    $pref->{ncos_id} = $tmp->id;
                }
            } elsif($pref->attribute eq "adm_ncos") {
                if ($pref_values->{adm_ncos_id} &&
                    (my $tmp = $c->stash->{ncos_levels_rs}
                        ->find($pref_values->{adm_ncos_id}) )) {
                    $pref->{adm_ncos_id} = $tmp->id;
                }
            } elsif($pref->attribute eq "adm_cf_ncos") {
                if ($pref_values->{adm_cf_ncos_id} &&
                    (my $tmp = $c->stash->{ncos_levels_rs}
                        ->find($pref_values->{adm_cf_ncos_id}) )) {
                    $pref->{adm_cf_ncos_id} = $tmp->id;
                }
            } elsif($pref->attribute eq "ncos_set") {
                if ($pref_values->{ncos_set_id} &&
                    (my $tmp = $c->stash->{ncos_sets_rs}
                        ->find($pref_values->{ncos_set_id}) )) {
                    $pref->{ncos_set_id} = $tmp->id;
                }
            } elsif($pref->attribute eq "adm_ncos_set") {
                if ($pref_values->{adm_ncos_set_id} &&
                    (my $tmp = $c->stash->{ncos_sets_rs}
                        ->find($pref_values->{adm_ncos_set_id}) )) {
                    $pref->{adm_ncos_set_id} = $tmp->id;
                }
            } elsif($pref->attribute eq "adm_cf_ncos_set") {
                if ($pref_values->{adm_cf_ncos_set_id} &&
                    (my $tmp = $c->stash->{ncos_sets_rs}
                        ->find($pref_values->{adm_cf_ncos_set_id}) )) {
                    $pref->{adm_cf_ncos_set_id} = $tmp->id;
                }
            } elsif($pref->attribute eq "emergency_mapping_container") {
                if ($pref_values->{emergency_mapping_container_id} &&
                    (my $tmp = $c->stash->{emergency_mapping_containers_rs}
                        ->find($pref_values->{emergency_mapping_container_id}) )) {
                    $pref->{emergency_mapping_container_id} = $tmp->id;
                }
            } elsif($pref->attribute eq "allowed_ips") {
                $pref->{allowed_ips_group_id} = $pref_values->{allowed_ips_grp};
                $pref->{allowed_ips_rs} = $c->model('DB')->resultset('voip_allowed_ip_groups')
                    ->search_rs({ group_id => $pref_values->{allowed_ips_grp} });
            } elsif($pref->attribute eq "man_allowed_ips") {
                $pref->{man_allowed_ips_group_id} = $pref_values->{man_allowed_ips_grp};
                $pref->{man_allowed_ips_rs} = $c->model('DB')->resultset('voip_allowed_ip_groups')
                    ->search_rs({ group_id => $pref_values->{man_allowed_ips_grp} });
            } elsif($c->stash->{subscriber} &&
                  ($pref->attribute eq "block_in_list" || $pref->attribute eq "block_out_list")) {
                foreach my $v(@values) {
                    my $prefix = "";
                    if($v =~ /^\#/) {
                        $v =~ s/^\#//;
                        $prefix = "#";
                    }

                    if($c->user->roles eq "subscriberadmin" || $c->user->roles eq "subscriber") {
                        $v = NGCP::Panel::Utils::Subscriber::apply_rewrite(
                            c => $c, subscriber => $c->stash->{subscriber}, number => $v, direction => 'caller_out'
                        );
                    }
                    $v = $prefix . $v;
                }
            }

            if($pref->data_type eq "enum") {
                $pref->{enums} = [];
                my @enums = $pref->voip_preferences_enums->all;
                push @{ $pref->{enums} }, @enums;
            }

            if($pref->max_occur != 1) {
                $pref->{value} = \@values;
            } else {
                $pref->{value} = $values[0];
            }
        }
        $group->{prefs} = \@group_prefs;
    }
    $c->stash(pref_groups => \@pref_groups);
}

sub create_preference_form {
    my %params = @_;

    my $c = $params{c};
    my $pref_rs = $params{pref_rs};
    my $base_uri = $params{base_uri};
    my $edit_uri = $params{edit_uri};
    my $enums    = $params{enums};
    my $blob_rs = $params{blob_rs};

    my $aip_grp_rs;
    my $aip_group_id;
    my $man_aip_grp_rs;
    my $man_aip_group_id;

    my $delete_param = $c->request->params->{delete};
    my $deactivate_param = $c->request->params->{deactivate};
    my $activate_param = $c->request->params->{activate};
    my $param_id = $delete_param || $deactivate_param || $activate_param;
    # only one parameter is processed at a time (?)
    if($param_id) {
        my $rs = $pref_rs->find($param_id);
        if($rs) {
            if($rs->attribute_id != $c->stash->{preference_meta}->id) {
                # Invalid param (dom_pref does not belong to current pref)
            } elsif($delete_param) {
                $rs->delete();
            } elsif ($deactivate_param) {
                $rs->update({value => "#".$rs->value});
            } elsif ($activate_param) {
                my $new_value = $rs->value;
                $new_value =~ s/^#//;
                $rs->update({value => $new_value});
            }
        }
    }

    my $preselected_value = undef;
    if ($c->stash->{preference_meta}->attribute eq "rewrite_rule_set") {
        my $rewrite_caller_in_dpid = $pref_rs->search({
                'attribute.attribute' => 'rewrite_caller_in_dpid'
            },{
                join => 'attribute'
            })->first;
        if (defined $rewrite_caller_in_dpid && (
            my $tmp = $preselected_value = $c->stash->{rwr_sets_rs}->search({
                    caller_in_dpid => $rewrite_caller_in_dpid->value,
                })->first
        )) {
            $preselected_value = $tmp->id;
        }
    } elsif ($c->stash->{preference_meta}->attribute eq "cdr_export_sclidui_rwrs") {

        my $rwrs_id_pref = $pref_rs->search({
                'attribute.attribute' => $c->stash->{preference_meta}->attribute . '_id'
            },{
                join => 'attribute'
            })->first;
        if (defined $rwrs_id_pref && (
            my $tmp = $preselected_value = $c->stash->{rwr_sets_rs}->search({
                    id => $rwrs_id_pref->value,
                })->first
        )) {
            $preselected_value = $tmp->id;
        }
    } elsif ($c->stash->{preference_meta}->attribute eq "ncos") {
        my $ncos_id_preference = $pref_rs->search({
                'attribute.attribute' => 'ncos_id'
            },{
                join => 'attribute'
            })->first;
        if (defined $ncos_id_preference) {
            $preselected_value = $ncos_id_preference->value;
        }
    } elsif ($c->stash->{preference_meta}->attribute eq "adm_ncos") {
        my $ncos_id_preference = $pref_rs->search({
                'attribute.attribute' => 'adm_ncos_id'
            },{
                join => 'attribute'
            })->first;
        if (defined $ncos_id_preference) {
            $preselected_value = $ncos_id_preference->value;
        }
    } elsif ($c->stash->{preference_meta}->attribute eq "adm_cf_ncos") {
        my $ncos_id_preference = $pref_rs->search({
                'attribute.attribute' => 'adm_cf_ncos_id'
            },{
                join => 'attribute'
            })->first;
        if (defined $ncos_id_preference) {
            $preselected_value = $ncos_id_preference->value;
        }
    } elsif ($c->stash->{preference_meta}->attribute =~ /^((adm_)?(cf_)?ncos_set)$/) {
        my $ncos_id_preference = $pref_rs->search({
                'attribute.attribute' => $1.'_id'
            },{
                join => 'attribute'
            })->first;
        if (defined $ncos_id_preference) {
            $preselected_value = $ncos_id_preference->value;
        }
    } elsif ($c->stash->{preference_meta}->attribute eq "emergency_mapping_container") {
        my $container_id_preference = $pref_rs->search({
                'attribute.attribute' => 'emergency_mapping_container_id'
            },{
                join => 'attribute'
            })->first;
        if (defined $container_id_preference) {
            $preselected_value = $container_id_preference->value;
        }
    } elsif ($c->stash->{preference_meta}->attribute eq "allowed_ips") {
        my $allowed_ips_grp = $pref_rs->search({
                'attribute.attribute' => 'allowed_ips_grp'
            },{
                join => 'attribute'
            })->first;
        if (defined $allowed_ips_grp) {
            $aip_group_id = $allowed_ips_grp->value;
            $aip_grp_rs = $c->model('DB')->resultset('voip_allowed_ip_groups')
                ->search({ group_id => $aip_group_id });
        }
        my $delete_aig_param = $c->request->params->{delete_aig};
        if($delete_aig_param) {
            my $result = $aip_grp_rs->find($delete_aig_param);
            if($result) {
                $result->delete;
                unless ($aip_grp_rs->first) { #its empty
                    my $allowed_ips_grp_preference = $pref_rs->search({
                        'attribute.attribute' => 'allowed_ips_grp'
                    },{
                        join => 'attribute'
                    })->first;
                    $allowed_ips_grp_preference->delete
                        if (defined $allowed_ips_grp_preference);
                }
            }
        }
    } elsif ($c->stash->{preference_meta}->attribute eq "man_allowed_ips") {
        my $man_allowed_ips_grp = $pref_rs->search({
                'attribute.attribute' => 'man_allowed_ips_grp'
            },{
                join => 'attribute'
            })->first;
        if (defined $man_allowed_ips_grp) {
            $man_aip_group_id = $man_allowed_ips_grp->value;
            $man_aip_grp_rs = $c->model('DB')->resultset('voip_allowed_ip_groups')
                ->search({ group_id => $man_aip_group_id });
        }
        my $delete_man_aig_param = $c->request->params->{delete_man_aig};
        if($delete_man_aig_param) {
            my $result = $man_aip_grp_rs->find($delete_man_aig_param);
            if($result) {
                $result->delete;
                unless ($man_aip_grp_rs->first) { #its empty
                    my $man_allowed_ips_grp_preference = $pref_rs->search({
                        'attribute.attribute' => 'man_allowed_ips_grp'
                    },{
                        join => 'attribute'
                    })->first;
                    $man_allowed_ips_grp_preference->delete
                        if (defined $man_allowed_ips_grp_preference);
                }
            }
        }
    } elsif ($c->stash->{preference_meta}->data_type eq 'enum') {
        $preselected_value = undef;
        if ($c->stash->{preference}->first) {
            $preselected_value = $c->stash->{preference}->first->value unless ($c->stash->{preference_meta}->data_type eq 'blob');
        }
    } elsif ($c->stash->{preference_meta}->max_occur == 1) {
        if ($c->stash->{preference}->first) {
            $preselected_value = $c->stash->{preference}->first->value unless ($c->stash->{preference_meta}->data_type eq 'blob');
        }
    }

    # this form is somewhat special, treat it without caching
    my $form = NGCP::Panel::Form::Preferences->new({
        ctx => $c,
        fields_data => [{
            meta => $c->stash->{preference_meta},
            enums => $enums,
            rwrs_rs => $c->stash->{rwr_sets_rs},
            hdrs_rs => $c->stash->{hdr_sets_rs},
            ncos_rs => $c->stash->{ncos_levels_rs},
            ncos_sets_rs => $c->stash->{ncos_sets_rs},
            emergency_mapping_containers_rs => $c->stash->{emergency_mapping_containers_rs},
            sound_rs => $c->stash->{sound_sets_rs},
            contract_sound_rs => $c->stash->{contract_sound_sets_rs},
        }],
    });
    $form->create_structure([$c->stash->{preference_meta}->attribute]);
    # we have to translate this form separately since it bypasses caching in NGCP::Panel::Form
    if ( $c->stash->{preference_meta}->attribute !~ '(ncos|ncos_set|sound_set|emergency_mapping_container)$' ) {
        NGCP::Panel::Utils::I18N->translate_form($c, $form);
    }

    my $posted = ($c->request->method eq 'POST');
    if($posted && $c->stash->{preference_meta}->data_type eq 'blob') {
        # construct the form field name for blob data types,
        # since we need to pass the Catalyst::Upload object to req params
        my $field_name = $c->stash->{preference_meta}->attribute . '.file';
        $c->req->params->{$field_name} = $c->req->upload($field_name) if ($c->req->upload($field_name));
    }
    $form->process(
        posted => $posted,
        params => $c->request->params,
        item => { $c->stash->{preference_meta}->attribute => $preselected_value },
    );
    NGCP::Panel::Utils::Navigation::check_form_buttons(
        c => $c,
        form => $form,
        fields => {},
        back_uri => $c->req->uri,
    );

    # logging
    my %log_data = %{$c->request->params};
    # subscriber prefs
    if ($c->stash->{subscriber}) {
        %log_data = ( %log_data,
                      type          => 'subscriber',
                      subscriber_id => $c->stash->{subscriber}->id,
                      uuid          => $c->stash->{subscriber}->uuid,
                    );
    # domain prefs
    } elsif ($c->stash->{domain}) {
        %log_data = ( %log_data,
                      type      => 'domain',
                      domain_id => $c->stash->{domain}{id},
                      domain    => $c->stash->{domain}{domain},
                    );
    # customer prefs
    } elsif ($c->stash->{contract}) {
        %log_data = ( %log_data,
                      type        => 'customer',
                      customer_id => $c->stash->{contract}->id,
                      reseller_id => $c->stash->{contract}->contact->reseller_id,
                    );
    # peering prefs
    } elsif ($c->stash->{group} && $c->stash->{server}) {
        %log_data = ( %log_data,
                      type            => 'peer',
                      peer_group_id   => $c->stash->{group}{id},
                      peer_group_name => $c->stash->{group}{name},
                      peer_host_id    => $c->stash->{server}{id},
                      peer_host_name  => $c->stash->{server}{name},
                    );
    } elsif ($c->stash->{reseller}) {
        %log_data = ( %log_data,
                      type            => 'reseller',
                      reseller_id => $c->stash->{reseller}->id,
                    );
    } elsif ($c->stash->{devmod}) {
        %log_data = ( %log_data,
                      type            => 'dev',
                      device_id       => $c->stash->{devmod}->{id},
                      device_vendor   => $c->stash->{devmod}->{vendor},
                      device_model    => $c->stash->{devmod}->{model},
                      reseller_id     => $c->stash->{devmod}->{reseller_id},
                    );
    } elsif ($c->stash->{devprof}) {
        %log_data = ( %log_data,
                      type            => 'devprof',
                      device_id       => $c->stash->{devprof}->{id},
                      device_vendor   => $c->stash->{devprof}->{config_id},
                      device_model    => $c->stash->{devprof}->{name},
                    );
    } elsif ($c->stash->{pbx_device}) {
        %log_data = ( %log_data,
                      type            => 'fielddev',
                      device_id       => $c->stash->{pbx_device}->{id},
                      device_vendor   => $c->stash->{pbx_device}->{profile_id},
                      device_model    => $c->stash->{pbx_device}->{identifier},
                    );
    }

    if($posted && $form->validated) {
        my $preference_id = $c->stash->{preference}->first ? $c->stash->{preference}->first->id : undef;
        my $attribute = $c->stash->{preference_meta}->attribute;
        if ($attribute eq "cli" && $c->user->roles eq 'subscriberadmin') {
            my $cli = $form->field($attribute)->value;
            my @allowed_cli_numbers = $c->model('DB')->resultset('voip_dbaliases')->search({
                'subscriber.account_id' => $c->stash->{subscriber}->contract_id,
            },{
                select => ['me.username'],
                as => ['number'],
                join => 'subscriber',
                result_class => 'DBIx::Class::ResultClass::HashRefInflator',
            })->all;
            unless (any { $_->{number} eq $cli } @allowed_cli_numbers) {
                my $err_msg = "Only numbers that belong to the customer can be assigned as";
                NGCP::Panel::Utils::Message::error(
                    c => $c,
                    error => $err_msg . $attribute,
                    data  => \%log_data,
                    desc  => $c->loc($err_msg . ' [_1]', $attribute),
                );
                $c->response->redirect($base_uri);
                return 1;
            }
            try {
                $pref_rs->update_or_create({
                    id => $preference_id,
                    attribute_id => $c->stash->{preference_meta}->id,
                    value => $form->field($attribute)->value,
                });
                NGCP::Panel::Utils::Message::info(
                    c => $c,
                    data  => \%log_data,
                    desc  => $c->loc('Preference [_1] successfully updated', $attribute),
                );
            } catch($e) {
               NGCP::Panel::Utils::Message::error(
                    c => $c,
                    error => $e,
                    data  => \%log_data,
                    desc  => $c->loc('Failed to update preference [_1]', $attribute),
                );
            }
            $c->response->redirect($base_uri);
            return 1;
        } elsif ($attribute eq "allowed_ips") {
            unless(validate_ipnet($form->field($attribute))) {
                goto OUT;
            }

            unless (defined $aip_group_id) {
                try {
                    my $new_group = $c->model('DB')->resultset('voip_aig_sequence')
                        ->create({});
                    my $aig_preference_id = $c->model('DB')
                        ->resultset('voip_preferences')
                        ->find({ attribute => 'allowed_ips_grp' })
                        ->id;
                    $pref_rs->create({
                            value => $new_group->id,
                            attribute_id => $aig_preference_id,
                        });
                    $aip_group_id = $new_group->id;
                    $aip_grp_rs = $c->model('DB')->resultset('voip_allowed_ip_groups')
                        ->search({ group_id => $aip_group_id });
                    $c->model('DB')->resultset('voip_aig_sequence')->search_rs({
                            id => { '<' => $new_group->id },
                        })->delete_all;
                    NGCP::Panel::Utils::Message::info(
                        c => $c,
                        type => 'internal',
                        data => \%log_data,
                        desc => $c->loc('ip group sequence successfully generated'),
                    );
                } catch($e) {
                    NGCP::Panel::Utils::Message::error(
                        c => $c,
                        error => $e,
                        data => \%log_data,
                        desc  => $c->loc('Failed to generate ip group sequence'),
                    );
                    $c->response->redirect($base_uri);
                    return 1;
                }
            }
            try {
                $aip_grp_rs->create({
                    group_id => $aip_group_id,
                    ipnet => $form->field($attribute)->value,
                });
                NGCP::Panel::Utils::Message::info(
                    c => $c,
                    type => 'internal',
                    data => \%log_data,
                    desc => $c->loc('allowed_ip_grp successfully created'),
                );
            } catch($e) {
                NGCP::Panel::Utils::Message::error(
                    c => $c,
                    error => $e,
                    data  => \%log_data,
                    desc  => $c->loc('Failed to create allowed_ip_grp'),
                );
                $c->response->redirect($base_uri);
                return 1;
            }
        } elsif ($attribute eq "man_allowed_ips") {
            unless(validate_ipnet($form->field($attribute))) {
                goto OUT;
            }
            unless (defined $man_aip_group_id) {
                try {
                    my $new_group = $c->model('DB')->resultset('voip_aig_sequence')
                        ->create({});
                    my $man_aig_preference_id = $c->model('DB')
                        ->resultset('voip_preferences')
                        ->find({ attribute => 'man_allowed_ips_grp' })
                        ->id;
                    $pref_rs->create({
                            value => $new_group->id,
                            attribute_id => $man_aig_preference_id,
                        });
                    $man_aip_group_id = $new_group->id;
                    $man_aip_grp_rs = $c->model('DB')->resultset('voip_allowed_ip_groups')
                        ->search({ group_id => $man_aip_group_id });
                    $c->model('DB')->resultset('voip_aig_sequence')->search_rs({
                            id => { '<' => $new_group->id },
                        })->delete_all;
                    NGCP::Panel::Utils::Message::info(
                        c => $c,
                        type => 'internal',
                        data => \%log_data,
                        desc => $c->loc('Manual ip group sequence successfully generated'),
                    );
                } catch($e) {
                    NGCP::Panel::Utils::Message::error(
                        c => $c,
                        error => $e,
                        data  => \%log_data,
                        desc  => $c->loc('Failed to generate manual ip group sequence'),
                    );
                    $c->response->redirect($base_uri);
                    return 1;
                }
            }
            try {
                $man_aip_grp_rs->create({
                    group_id => $man_aip_group_id,
                    ipnet => $form->field($attribute)->value,
                });
                NGCP::Panel::Utils::Message::info(
                    c => $c,
                    type => 'internal',
                    data => \%log_data,
                    desc => $c->loc('man_allowed_ip_grp successfully created'),
                );
            } catch($e) {
                NGCP::Panel::Utils::Message::error(
                    c => $c,
                    error => $e,
                    data  => \%log_data,
                    desc  => $c->loc('Failed to create man_allowed_ip_grp'),
                );
                $c->response->redirect($base_uri);
                return 1;
            }
        } elsif ($attribute eq "allowed_clis") {
            my $v = $form->field($attribute)->value;
            my $existing_cli = $pref_rs->search({
                attribute_id => $c->stash->{preference_meta}->id,
                value => $v
            });
            if ($existing_cli->first) {
                NGCP::Panel::Utils::Message::error(
                    c => $c,
                    error => $c->loc('Duplicate preference [_1]', $attribute),
                    data  => \%log_data,
                    desc  => $c->loc('Duplicate preference [_1]', $attribute),
                );
                $c->response->redirect($base_uri);
                return 1;
            }
            else {
                $pref_rs->create({
                    attribute_id => $c->stash->{preference_meta}->id,
                    value => $form->values->{$c->stash->{preference_meta}->attribute},
                });
                NGCP::Panel::Utils::Message::info(
                    c => $c,
                    data => \%log_data,
                    desc => $c->loc('Preference [_1] successfully created', $attribute),
                );
            }
        } elsif ($c->stash->{preference_meta}->max_occur != 1) {
            if($c->stash->{subscriber} &&
               ($c->stash->{preference_meta}->attribute eq "block_in_list" || $c->stash->{preference_meta}->attribute eq "block_out_list")) {
                my $v = $form->values->{$c->stash->{preference_meta}->attribute};

                if($c->user->roles eq "subscriberadmin" || $c->user->roles eq "subscriber") {
                    $v =~ s/^(.+?)([*\[].*$)/$1/; # strip any trailing shell pattern stuff
                    my $suffix = $2 // "";
                    $form->values->{$c->stash->{preference_meta}->attribute} = NGCP::Panel::Utils::Subscriber::apply_rewrite(
                        c => $c, subscriber => $c->stash->{subscriber}, number => $v, direction => 'callee_in'
                    );

                    # rewrite it back for immediate display
                    $v = $form->values->{$c->stash->{preference_meta}->attribute};
                    $v = NGCP::Panel::Utils::Subscriber::apply_rewrite(
                        c => $c, subscriber => $c->stash->{subscriber}, number => $v, direction => 'caller_out'
                    );

                    # restore stripped shell pattern stuff
                    $form->values->{$c->stash->{preference_meta}->attribute} .= $suffix;
                    $v .= $suffix;

                }
            }
            try {
                $pref_rs->create({
                    attribute_id => $c->stash->{preference_meta}->id,
                    value => $form->values->{$c->stash->{preference_meta}->attribute},
                });
                NGCP::Panel::Utils::Message::info(
                    c => $c,
                    data => \%log_data,
                    desc => $c->loc('Preference [_1] successfully created', $attribute),
                );
            } catch($e) {
                NGCP::Panel::Utils::Message::error(
                    c => $c,
                    error => $e,
                    data  => \%log_data,
                    desc  => $c->loc('Failed to create preference [_1]', $attribute),
                );
                $c->response->redirect($base_uri);
                return 1;
            }
        } elsif ($attribute eq "rewrite_rule_set") {
            my $selected_rwrs = $c->stash->{rwr_sets_rs}->find(
                $form->field($attribute)->value
            );
            set_rewrite_preferences(
                c             => $c,
                rwrs_result   => $selected_rwrs,
                pref_rs       => $pref_rs,
            );
            NGCP::Panel::Utils::Message::info(
                c => $c,
                data => \%log_data,
                desc => $c->loc('Preference [_1] successfully updated', $attribute),
            );
            $c->response->redirect($base_uri);
            return 1;
        } elsif ($attribute eq "cdr_export_sclidui_rwrs") {
            my $selected_rwrs = $c->stash->{rwr_sets_rs}->find(
                $form->field($attribute)->value
            );
            set_rewrite_id_preference(
                c             => $c,
                rwrs_result   => $selected_rwrs,
                pref_rs       => $pref_rs,
                rwrs_pref_attribute => $attribute,
            );
            NGCP::Panel::Utils::Message::info(
                c => $c,
                data => \%log_data,
                desc => $c->loc('Preference [_1] successfully updated', $attribute),
            );
            $c->response->redirect($base_uri);
            return 1;
        } elsif ($attribute eq "ncos" || $attribute eq "adm_ncos" || $attribute eq "adm_cf_ncos") {
            my $selected_level = $c->stash->{ncos_levels_rs}->find(
                $form->field($attribute)->value
            );
            my $attribute_id = $c->model('DB')->resultset('voip_preferences')
                ->find({attribute => $attribute."_id"})->id;

            try {
                my $preference = $pref_rs->search({ attribute_id => $attribute_id });
                if(!defined $selected_level) {
                    $preference->first->delete if $preference->first;
                } elsif($preference->first) {
                    $preference->first->update({ value => $selected_level->id });
                } else {
                    $preference->create({ value => $selected_level->id });
                }
                NGCP::Panel::Utils::Message::info(
                    c => $c,
                    data => \%log_data,
                    desc => $c->loc('Preference [_1] successfully updated', $attribute),
                );
            } catch($e) {
                NGCP::Panel::Utils::Message::error(
                    c => $c,
                    error => $e,
                    data  => \%log_data,
                    desc  => $c->loc('Failed to update preference [_1]', $attribute),
                );
                $c->response->redirect($base_uri);
                return 1;
            }
            $c->response->redirect($base_uri);
            return 1;
        } elsif ($attribute eq "ncos_set" || $attribute eq "adm_ncos_set" || $attribute eq "adm_cf_ncos_set") {
            my $selected_set = $c->stash->{ncos_sets_rs}->find(
                $form->field($attribute)->value
            );
            my $attribute_id = $c->model('DB')->resultset('voip_preferences')
                ->find({attribute => $attribute."_id"})->id;

            try {
                my $preference = $pref_rs->search({ attribute_id => $attribute_id });
                if(!defined $selected_set) {
                    $preference->first->delete if $preference->first;
                } elsif($preference->first) {
                    $preference->first->update({ value => $selected_set->id });
                } else {
                    $preference->create({ value => $selected_set->id });
                }
                NGCP::Panel::Utils::Message::info(
                    c => $c,
                    data => \%log_data,
                    desc => $c->loc('Preference [_1] successfully updated', $attribute),
                );
            } catch($e) {
                NGCP::Panel::Utils::Message::error(
                    c => $c,
                    error => $e,
                    data  => \%log_data,
                    desc  => $c->loc('Failed to update preference [_1]', $attribute),
                );
                $c->response->redirect($base_uri);
                return 1;
            }
            $c->response->redirect($base_uri);
            return 1;
        } elsif ($attribute eq "emergency_mapping_container") {
            my $selected_container = $c->stash->{emergency_mapping_containers_rs}->find(
                $form->field($attribute)->value
            );
            my $attribute_id = $c->model('DB')->resultset('voip_preferences')
                ->find({attribute => $attribute."_id"})->id;

            try {
                my $preference = $pref_rs->search({ attribute_id => $attribute_id });
                if(!defined $selected_container) {
                    $preference->first->delete if $preference->first;
                } elsif($preference->first) {
                    $preference->first->update({ value => $selected_container->id });
                } else {
                    $preference->create({ value => $selected_container->id });
                }
                NGCP::Panel::Utils::Message::info(
                    c => $c,
                    data => \%log_data,
                    desc => $c->loc('Preference [_1] successfully updated', $attribute),
                );
            } catch($e) {
                NGCP::Panel::Utils::Message::error(
                    c => $c,
                    error => $e,
                    data  => \%log_data,
                    desc  => $c->loc('Failed to update preference [_1]', $attribute),
                );
                $c->response->redirect($base_uri);
                return 1;
            }
            $c->response->redirect($base_uri);
            return 1;
        } elsif ($attribute eq "sound_set") {
            my $selected_set = $c->stash->{sound_sets_rs}->find(
                $form->field($attribute)->value
            );

            try {
                my $preference = $pref_rs->search({
                    attribute_id => $c->stash->{preference_meta}->id });
                if(!defined $selected_set) {
                    $preference->first->delete if $preference->first;
                } elsif($preference->first) {
                    $preference->first->update({ value => $selected_set->id });
                } else {
                    $preference->create({ value => $selected_set->id });
                }
                NGCP::Panel::Utils::Message::info(
                    c => $c,
                    data => \%log_data,
                    desc => $c->loc('Preference [_1] successfully updated', $attribute),
                );
            } catch($e) {
                NGCP::Panel::Utils::Message::error(
                    c => $c,
                    error => $e,
                    data  => \%log_data,
                    desc  => $c->loc('Failed to update preference [_1]', $attribute),
                );
                $c->response->redirect($base_uri);
                return 1;
            }
            $c->response->redirect($base_uri);
            return 1;
        } elsif ($attribute eq "contract_sound_set") {
            my $selected_set = $c->stash->{contract_sound_sets_rs}->find(
                $form->field($attribute)->value
            );

            try {
                my $preference = $pref_rs->search({
                    attribute_id => $c->stash->{preference_meta}->id });
                if(!defined $selected_set) {
                    $preference->first->delete if $preference->first;
                } elsif($preference->first) {
                    $preference->first->update({ value => $selected_set->id });
                } else {
                    $preference->create({ value => $selected_set->id });
                }
                NGCP::Panel::Utils::Message::info(
                    c => $c,
                    data => \%log_data,
                    desc => $c->loc('Preference [_1] successfully updated', $attribute),
                );
            } catch($e) {
                NGCP::Panel::Utils::Message::error(
                    c => $c,
                    error => $e,
                    data  => \%log_data,
                    desc  => $c->loc('Failed to update preference [_1]', $attribute),
                );
                $c->response->redirect($base_uri);
                return 1;
            }
            $c->response->redirect($base_uri);
            return 1;
        } elsif ($attribute eq "lock") {
            my $v = $form->field($attribute)->value;
            #undef $v if (defined $v && $v eq '');
            try {
                NGCP::Panel::Utils::Subscriber::lock_provisoning_voip_subscriber(
                        c => $c,
                        prov_subscriber => $c->stash->{subscriber}->provisioning_voip_subscriber,
                        level => $v,
                    );
                NGCP::Panel::Utils::Message::info(
                    c => $c,
                    data  => \%log_data,
                    desc  => $c->loc('Preference [_1] successfully updated', $attribute),
                );
            } catch($e) {
                   NGCP::Panel::Utils::Message::error(
                        c => $c,
                        error => $e,
                        data  => \%log_data,
                        desc  => $c->loc('Failed to update preference [_1]', $attribute),
                    );
                    $c->response->redirect($base_uri);
                    return 1;
            }
            $c->response->redirect($base_uri);
            return 1;
        } else {
            if( ($c->stash->{preference_meta}->data_type ne 'enum' &&
                (!defined $form->field($attribute)->value || $form->field($attribute)->value eq '')) ||
                ($c->stash->{preference_meta}->data_type eq 'enum' &&
                ! defined $form->field($attribute)->value)
                ) {
                try {
                    my $preference = $pref_rs->find($preference_id);
                    $preference->delete if $preference;
                    NGCP::Panel::Utils::Message::info(
                        c => $c,
                        data => \%log_data,
                        desc => $c->loc('Preference [_1] successfully deleted', $attribute),
                    );
                } catch($e) {
                    NGCP::Panel::Utils::Message::error(
                        c => $c,
                        error => $e,
                        data  => \%log_data,
                        desc  => $c->loc('Failed to delete preference [_1]', $attribute),
                    );
                    $c->response->redirect($base_uri);
                    return 1;
                }
            } elsif($c->stash->{preference_meta}->data_type eq 'boolean' &&
                    $form->field($attribute)->value == 0) {
                try {
                    my $preference = $pref_rs->find($preference_id);
                    $preference->delete if $preference;
                    NGCP::Panel::Utils::Message::info(
                        c => $c,
                        data => \%log_data,
                        desc => $c->loc('Preference [_1] successfully deleted', $attribute),
                    );
                } catch($e) {
                    NGCP::Panel::Utils::Message::error(
                        c => $c,
                        error => $e,
                        data  => \%log_data,
                        desc  => $c->loc('Failed to delete preference [_1]', $attribute),
                    );
                    $c->response->redirect($base_uri);
                    return 1;
                }
            } elsif($c->stash->{preference_meta}->data_type eq 'blob') {
                try {
                    my $preference = $pref_rs->search({ attribute_id => $c->stash->{preference_meta}->id });
                    my $file = $form->field("$attribute.file")->value;
                    my $content_type = $form->field("$attribute.content_type")->value;
                    if ($c->req->body_parameters->{"$attribute.delete"}) {
                        $preference->delete if $preference;
                    } elsif ($c->req->body_parameters->{"$attribute.download"}) {
                        my $blob = $blob_rs->search({ preference_id => $preference->first->id });
                        my $data = $blob->first->value;
                        my $ft = File::Type->new();
                        $c->response->header('Content-Disposition' => 'attachment; filename="' . $blob->first->id . '-' . $attribute . '"');
                        $c->response->content_type($ft->mime_type($blob->first->value) || $blob->first->content_type);
                        $c->response->body($data);
                        return 1;
                    } elsif ($preference->first) {
                        my $blob = $blob_rs->search({ preference_id => $preference->first->id });
                        if ($blob->first) {
                            $blob->update({
                                preference_id => $preference->first->id,
                                $file ? (value => $file->slurp) : (),
                                $content_type ? (content_type => $content_type) : (),
                            });
                        } else {
                            $blob_rs->create({
                                preference_id => $preference->first->id,
                                value => ($file ? $file->slurp : undef),
                                content_type => $content_type,
                            });
                        }
                    } else {
                        $preference->create({ value => 0 });
                        $blob_rs->create({
                            preference_id => $preference->first->id,
                            value => ($file ? $file->slurp : undef),
                            content_type => $content_type,
                        });
                    }
                    NGCP::Panel::Utils::Message::info(
                        c => $c,
                        data  => \%log_data,
                        desc  => $c->loc('Preference [_1] successfully updated', $attribute),
                    );
                } catch($e) {
                   NGCP::Panel::Utils::Message::error(
                        c => $c,
                        error => $e,
                        data  => \%log_data,
                        desc  => $c->loc('Failed to update preference [_1]', $attribute),
                    );
                    $c->response->redirect($base_uri);
                    return 1;
                }
            } else {
                try {
                    $pref_rs->update_or_create({
                        id => $preference_id,
                        attribute_id => $c->stash->{preference_meta}->id,
                        value => $form->field($attribute)->value,
                    });
                    NGCP::Panel::Utils::Message::info(
                        c => $c,
                        data  => \%log_data,
                        desc  => $c->loc('Preference [_1] successfully updated', $attribute),
                    );
                } catch($e) {
                   NGCP::Panel::Utils::Message::error(
                        c => $c,
                        error => $e,
                        data  => \%log_data,
                        desc  => $c->loc('Failed to update preference [_1]', $attribute),
                    );
                    $c->response->redirect($base_uri);
                    return 1;
                }
            }
            $c->response->redirect($base_uri);
            return 1;
         }
    }

    OUT:

    my $preference_values = [];
    foreach my $p ( $c->stash->{preference}->all ) {
        my $v = $p->value;
        $v =~ s/^\#//;
        if( ($c->user->roles eq "subscriberadmin" || $c->user->roles eq "subscriber") &&
            $c->stash->{subscriber} &&
            (   $c->stash->{preference_meta}->attribute eq "block_in_list" ||
                $c->stash->{preference_meta}->attribute eq "block_out_list" )
            ) {
            $v = NGCP::Panel::Utils::Subscriber::apply_rewrite(
                c => $c, subscriber => $c->stash->{subscriber}, number => $v, direction => 'caller_out',
            );
        }

        push @{ $preference_values }, {
                id => $p->id,
                value => $v,
                disabled => !!($p->value =~ m/^\#/),
            };
    }

    $form->process if ($posted && $form->validated);
    $c->stash(form              => $form,
              aip_grp_rs        => $aip_grp_rs,
              man_aip_grp_rs    => $man_aip_grp_rs,
              preference_values => $preference_values);

    return 1;
}

sub set_rewrite_preferences {
    my %params = @_;

    my $c             = $params{c};
    my $rwrs_result   = $params{rwrs_result};
    my $pref_rs       = $params{pref_rs};

    for my $dprules(qw/
                    callee_in_dpid caller_in_dpid
                    callee_out_dpid caller_out_dpid
                    callee_lnp_dpid caller_lnp_dpid/) {

        my $attribute_id = $c->model('DB')->resultset('voip_preferences')
            ->find({attribute => "rewrite_$dprules"})->id;
        my $preference = $pref_rs->search({
            attribute_id => $attribute_id,
        });

        if(!defined $rwrs_result) {
            $preference->first->delete if $preference->first;
        } elsif($preference->first) {
            $preference->first->update({ value => $rwrs_result->$dprules });
        } else {
            $preference->create({ value => $rwrs_result->$dprules });
        }
    }

}

sub set_rewrite_id_preference {
    my %params = @_;

    my $c = $params{c};
    my $rwrs_result = $params{rwrs_result};
    my $pref_rs = $params{pref_rs};
    my $rwrs_pref_attribute = $params{rwrs_pref_attribute};

    my $attribute_id = $c->model('DB')->resultset('voip_preferences')
        ->find({attribute => $rwrs_pref_attribute . '_id'})->id;
    my $preference = $pref_rs->search({
        attribute_id => $attribute_id,
    });

    if(!defined $rwrs_result) {
        $preference->first->delete if $preference->first;
    } elsif($preference->first) {
        $preference->first->update({ value => $rwrs_result->id });
    } else {
        $preference->create({ value => $rwrs_result->id });
    }

}

sub get_usr_preferences_rs {
    my %params = @_;

    my $c = $params{c};
    my $attribute = $params{attribute};
    my $prov_subscriber = $params{prov_subscriber};
    my $schema = $params{schema} // $c->model('DB');
    my $get_rows = $params{get_rows};

    my $pref_rs = $schema->resultset('voip_usr_preferences')->search({
            'attribute.usr_pref' => 1,
            $attribute ? ( 'attribute.attribute' => (('ARRAY' eq ref $attribute) ? { '-in' => $attribute } : $attribute ) ) : ()  ,
            $prov_subscriber ? ('me.subscriber_id' => $prov_subscriber->id) : (),
        },{
            '+select' => ['attribute.attribute'],
            '+as' => ['attribute'],
            'join' => 'attribute',
    });

    return $pref_rs;
}

sub get_preferences_rs {
    my %params = @_;

    my $c = $params{c};
    my $preferences_type = $params{type};
    my $attribute = $params{attribute};
    my $item_id = $params{id};
    my $schema = $params{schema} // $c->model('DB');

    my %config = (
        'usr'      => [qw/voip_usr_preferences usr_pref subscriber_id/],
        'dom'      => [qw/voip_dom_preferences dom_pref domain_id/],
        'prof'     => [qw/voip_prof_preferences prof_pref profile_id/],
        'peer'     => [qw/voip_peer_preferences peer_pref peer_host_id/],
        'reseller' => [qw/reseller_preferences reseller_pref reseller_id/],
        'dev'      => [qw/voip_dev_preferences dev_pref device_id/],
        'devprof'  => [qw/voip_devprof_preferences devprof_pref profile_id/],
        'fielddev' => [qw/voip_fielddev_preferences fielddev_pref device_id/],
        'contract' => [qw/voip_contract_preferences contract_pref contract_id/],
        'contract_location' => [qw/voip_contract_preferences contract_location_pref location_id/],
    );
    my $pref_rs = $schema->resultset($config{$preferences_type}->[0])->search({
            'attribute.'.$config{$preferences_type}->[1] => 1,  ## no critic (ProhibitCommaSeparatedStatements)
            $attribute ? ( 'attribute.attribute' => (('ARRAY' eq ref $attribute) ? { '-in' => $attribute } : $attribute ) ) : ()  ,
            $item_id ? ('me.'.$config{$preferences_type}->[2] => $item_id) : (),
        },{
            '+select' => ['attribute.attribute'],
            '+as' => ['attribute'],
            'join' => 'attribute',
    });

    return $pref_rs;
}

sub get_preference_rs {
    my ($c, $type, $elem, $attr, $params) = @_;

    my $location_id     = $params->{location_id} // undef;

    my $rs;
    if($type eq "dom") {
        $rs = get_dom_preference_rs(
            c => $c,
            attribute => $attr,
            prov_domain => $elem,
        );
    } elsif($type eq "prof") {
        $rs = get_prof_preference_rs(
            c => $c,
            attribute => $attr,
            profile => $elem,
        );
    } elsif($type eq "usr") {
        $rs = get_usr_preference_rs(
            c => $c,
            attribute => $attr,
            prov_subscriber => $elem,
        );
    } elsif($type eq "peer") {
        $rs = get_peer_preference_rs(
            c => $c,
            attribute => $attr,
            peer_host => $elem,
        );
    } elsif($type eq "reseller") {
        $rs = get_reseller_preference_rs(
            c => $c,
            attribute => $attr,
            reseller => $elem,
        );
    } elsif($type eq "dev") {
        $rs = get_dev_preference_rs(
            c => $c,
            attribute => $attr,
            device => $elem,
        );
    } elsif($type eq "devprof") {
        $rs = get_devprof_preference_rs(
            c => $c,
            attribute => $attr,
            profile => $elem,
        );
    } elsif($type eq "fielddev") {
        $rs = get_fielddev_preference_rs(
            c => $c,
            attribute => $attr,
            device => $elem,
        );
    } elsif($type eq "contract") {
        $rs = get_contract_preference_rs(
            c => $c,
            attribute => $attr,
            contract => $elem,
            location_id => $location_id,
        );
    }
    return $rs;
}

sub get_chained_preference_rs {
    my ($c, $attr, $elem, $params) = @_;

    my $type_order_default = {
        'usr' => [qw/usr prof dom/],
    };
    my $elem_sub_type_id = {
        usr => {
            prof => $elem->voip_subscriber_profile,
            dom => $elem->domain,
        }
    };
    my $preference = $c->model('DB')
        ->resultset('voip_preferences')
        ->find({ attribute => $attr });

    return unless $preference;

    my $type_meta = $params->{type} // 'usr';
    my $type_order = $params->{order} // $type_order_default->{$type_meta};
    my $provisioning_subscriber = $params->{provisioning_subscriber};


    my $attribute_value_rs;
    my $preference_desc = { $preference->get_columns };
    foreach my $preference_type ( grep {$preference_desc->{$_.'_pref'} } @{$type_order} ) {
        my ($preference_elem_id, $preference_elem);
        if ($preference_type eq $type_meta){
            $preference_elem = $elem;
         } else {
            $preference_elem = $elem_sub_type_id->{$type_meta}->{$preference_type};
        }
        if ($preference_elem) {
            $preference_elem_id = $preference_elem->id;
        }
        if ($preference_elem_id) {
            #$attribute_value_rs = get_preferences_rs(
            #    c => $c,
            #    type => $preference_type,
            #    attribute => $attr,
            #    id => $preference_elem_id,
            #);
            $attribute_value_rs = get_preference_rs(
                $c,
                $preference_type,
                $preference_elem,
                $attr,
            );
            if ($attribute_value_rs->first) {
                return $attribute_value_rs;
            }
        }
    }
    return $attribute_value_rs;
}

sub get_usr_preference_rs {
    my %params = @_;

    my $c = $params{c};
    my $attribute = $params{attribute};
    my $prov_subscriber = $params{prov_subscriber};
    my $schema = $params{schema} // $c->model('DB');

    my $preference = $schema->resultset('voip_preferences')->find({
        attribute => $attribute,
        usr_pref => 1,
    });
    return unless $preference;

    return $preference->voip_usr_preferences unless $prov_subscriber;

    return $preference->voip_usr_preferences->search_rs({
        subscriber_id => $prov_subscriber->id,
    });
}

sub get_prof_preference_rs {
    my %params = @_;

    my $c = $params{c};
    my $attribute = $params{attribute};
    my $profile = $params{profile};
    my $schema = $params{schema} // $c->model('DB');

    my $pref_rs = $schema->resultset('voip_preferences')->find({
            attribute => $attribute, 'prof_pref' => 1,
        });
    return unless($pref_rs);
    $pref_rs = $pref_rs->voip_prof_preferences;
    if($profile) {
        # TODO: if profile is not set, it should return an rs with no entries?
        $pref_rs = $pref_rs->search({
                profile_id => $profile->id,
            });
    }
    return $pref_rs;
}

sub get_dom_preference_rs {
    my %params = @_;

    my $c = $params{c};
    my $attribute = $params{attribute};
    my $prov_domain = $params{prov_domain};

    my $preference = $c->model('DB')->resultset('voip_preferences')->find({
            attribute => $attribute, 'dom_pref' => 1,
        });
    return unless($preference);
    return $preference->voip_dom_preferences->search_rs({
            domain_id => $prov_domain->id,
        });
}

sub get_peer_preference_rs {
    my %params = @_;

    my $c = $params{c};
    my $attribute = $params{attribute};
    my $host = $params{peer_host};

    my $preference = $c->model('DB')->resultset('voip_preferences')->find({
            attribute => $attribute, 'peer_pref' => 1,
        });
    return unless($preference);
    return $preference->voip_peer_preferences->search_rs({
            peer_host_id => $host->id,
        });
}

sub get_reseller_preference_rs {
    my %params = @_;

    my $c = $params{c};
    my $attribute = $params{attribute};
    my $reseller = $params{reseller};

    my $preference = $c->model('DB')->resultset('voip_preferences')->find({
            attribute => $attribute, 'reseller_pref' => 1,
        });
    return unless($preference);
    return $preference->reseller_preferences->search_rs({
            reseller_id => $reseller->id,
        });
}

sub get_dev_preference_rs {
    my %params = @_;

    my $c = $params{c};
    my $attribute = $params{attribute};
    my $device = $params{device};

    my $preference = $c->model('DB')->resultset('voip_preferences')->find({
            attribute => $attribute, 'dev_pref' => 1,
        });
    return unless($preference);
    return $preference->voip_dev_preferences->search_rs({
           device_id => $device->id,
        });
}

sub get_devprof_preference_rs {
    my %params = @_;

    my $c = $params{c};
    my $attribute = $params{attribute};
    my $profile = $params{profile};

    my $preference = $c->model('DB')->resultset('voip_preferences')->find({
            attribute => $attribute, 'devprof_pref' => 1,
        });
    return unless($preference);
    return $preference->voip_devprof_preferences->search_rs({
           profile_id => $profile->id,
        });
}

sub get_fielddev_preference_rs {
    my %params = @_;

    my $c = $params{c};
    my $attribute = $params{attribute};
    my $device = $params{device};

    my $preference = $c->model('DB')->resultset('voip_preferences')->find({
            attribute => $attribute, 'fielddev_pref' => 1,
        });
    return unless($preference);
    return $preference->voip_fielddev_preferences->search_rs({
           device_id => $device->id,
        });
}

sub get_contract_preference_rs {
    my %params = @_;

    my $c = $params{c};
    my $attribute = $params{attribute};
    my $contract = $params{contract};
    my $location_id = $params{location_id} || undef;

    my $preference = $c->model('DB')->resultset('voip_preferences')->find({
            attribute => $attribute,
            contract_pref => 1,
            contract_location_pref => $location_id ? 1 : 0,
        });
    return unless($preference);
    return $preference->voip_contract_preferences->search_rs({
            contract_id => $contract->id,
            location_id => $location_id,
        });
}

sub update_sems_peer_auth {
    my ($c, $prov_object, $type, $old_auth_prefs, $new_auth_prefs) = @_;
    # prov_object can be either peering or subscriber

    if(!is_peer_auth_active($c, $old_auth_prefs) &&
        is_peer_auth_active($c, $new_auth_prefs)) {

        NGCP::Panel::Utils::Sems::create_peer_registration(
            $c, $prov_object, $type, $new_auth_prefs);
    } elsif(is_peer_auth_active($c, $old_auth_prefs) &&
            !is_peer_auth_active($c, $new_auth_prefs)) {

        NGCP::Panel::Utils::Sems::delete_peer_registration(
            $c, $prov_object, $type, $old_auth_prefs);
    } elsif(is_peer_auth_active($c, $old_auth_prefs) &&
            is_peer_auth_active($c, $new_auth_prefs)){

        NGCP::Panel::Utils::Sems::update_peer_registration(
            $c, $prov_object, $type, $new_auth_prefs, $old_auth_prefs);
    }

    return;
}

sub get_peer_auth_params {
    my ($c, $prov_object, $type, $prefs) = @_;

    foreach my $attribute (qw/peer_auth_user peer_auth_hf_user peer_auth_realm peer_auth_registrar_server peer_auth_pass peer_auth_register/) {
        my $rs;
        if ($type eq 'subscribers') {
            $rs = get_usr_preference_rs(
                c => $c,
                attribute => $attribute,
                prov_subscriber => $prov_object
            );
        } elsif ($type eq 'peerings') {
            $rs = get_peer_preference_rs(
                c => $c,
                attribute => $attribute,
                peer_host => $prov_object
            );
        }
        $prefs->{$attribute} = $rs && $rs->first ? $rs->first->value : undef;
    }
}

sub is_peer_auth_active {
    my ($c, $prefs) = @_;
    if(defined $prefs->{peer_auth_register} && $prefs->{peer_auth_register} == 1 &&
       defined $prefs->{peer_auth_user} &&
       defined $prefs->{peer_auth_realm} &&
       defined $prefs->{peer_auth_pass}) {

        return 1;
    }
    return;
}

sub set_provisoning_voip_subscriber_first_int_attr_value {
    my %params = @_;

    my $c = $params{c};
    my $prov_subscriber= $params{prov_subscriber};
    my $new_value = $params{value};
    if (defined $new_value) {
        $new_value =~ s/^\s+|\s+$//g;
        undef $new_value if $new_value eq '';
    }
    my $attribute = $params{attribute};

    return unless $prov_subscriber;

    my $rs = get_usr_preference_rs(
        c => $c,
        prov_subscriber => $prov_subscriber,
        attribute => $attribute,
    );
    try {
        if($rs->first) {
            if(($new_value // 0) == 0) {
                $rs->first->delete;
            } else {
                $rs->first->update({ value => $new_value });
            }
        } elsif(($new_value // 0) > 0) {
            $rs->create({ value => $new_value });
        } # nothing to do for level 0, if no lock is set yet
    } catch($e) {
        $c->log->error("failed to set provisioning_voip_subscriber attribute '$attribute': $e");
        $e->rethrow;
    }
}

sub get_provisoning_voip_subscriber_first_int_attr_value {
    my %params = @_;

    my $c = $params{c};
    my $prov_subscriber= $params{prov_subscriber};
    my $attribute = $params{attribute};

    return unless $prov_subscriber;

    my $rs = get_usr_preference_rs(
        c => $c,
        prov_subscriber => $prov_subscriber,
        attribute => $attribute,
    );
    try {
        return ($rs->first ? $rs->first->value : undef);
    } catch($e) {
        $c->log->error("failed to get provisioning_voip_subscriber attribute '$attribute': $e");
        $e->rethrow;
    }
}

sub api_preferences_defs {
    my %params = @_;

    my $c = $params{c};
    my $schema = $params{schema} // $c->model('DB');
    my $preferences_group = $params{preferences_group};

    my $preferences = $c->model('DB')->resultset('voip_preferences')->search({
        internal => { '!=' => 1 }, # also fetch -1 for ncos, rwr
        $preferences_group => 1,
        $c->user->roles eq 'subscriberadmin'
            ? (expose_to_customer => 1) : (),
        $c->user->roles eq 'subscriber'
            ? (expose_to_subscriber => 1) : (),
    });

    my $resource = {};
    for my $pref($preferences->all) {
        my $fields = { $pref->get_inflated_columns };
        # remove internal fields
        delete @{$fields}{qw/type attribute expose_to_customer expose_to_subscriber internal peer_pref reseller_pref usr_pref dom_pref contract_pref contract_location_pref prof_pref voip_preference_groups_id id modify_timestamp/};
        $fields->{max_occur} = int($fields->{max_occur});
        $fields->{read_only} = JSON::Types::bool($fields->{read_only});
        if($fields->{data_type} eq "enum") {
            my @enums = $pref->voip_preferences_enums->search({
                $preferences_group => 1,
            })->all;
            $fields->{enum_values} = [];
            foreach my $enum(@enums) {
                my $efields = { $enum->get_inflated_columns };
                delete @{$efields}{qw/id preference_id usr_pref prof_pref dom_pref peer_pref reseller_pref contract_pref contract_location_pref/};
                $efields->{default_val} = JSON::Types::bool($efields->{default_val});
                push @{ $fields->{enum_values} }, $efields;
            }
        }
        if ($pref->attribute =~ m/^(cdr_export_sclidui_rwrs|rewrite_rule_set|ncos|adm_ncos|adm_cf_ncos|ncos_set|adm_ncos_set|adm_cf_ncos_set|emergency_mapping_container|sound_set|contract_sound_set|header_rule_set)$/) {
            $fields->{data_type} = 'string';
        }

        my $preference_group = $pref->voip_preference_group->name =~ s/([\[\]])/~$1/rg;
        my $label = $fields->{label} =~ s/([\[\]])/~$1/rg;
        my $description = $fields->{description} =~ s/([\[\]])/~$1/rg;
        $fields->{preference_group} = $c->loc($preference_group);
        $fields->{label} = $c->loc($label);
        $fields->{description} = $c->loc($description);
        $resource->{$pref->attribute} = $fields;
    }
    return $resource;
}

sub get_subscriber_allowed_prefs {
    my %params = @_;

    my $c = $params{c};
    my $schema = $params{schema} // $c->model('DB');
    my $prov_subs = $params{prov_subscriber};
    my $pref_list = $params{pref_list};

    my %allowed_prefs = map {$_ => 1} @{ $pref_list };

    if ($c->user->roles eq "subscriber" || $c->user->roles eq "subscriberadmin") {
        if ($prov_subs && $prov_subs->voip_subscriber_profile) {
            my $profile = $prov_subs->voip_subscriber_profile;
            my @result = $profile->profile_attributes->search_rs({
                'attribute.attribute' => { '-in' => $pref_list },
            },{
                join => 'attribute'
            })->get_column('attribute.attribute')->all;
            %allowed_prefs = map {$_ => 1} @result;
        }

    }

    return \%allowed_prefs
}

sub create_dynamic_preference {
    my ($c, $resource, %params) = @_;

    my $group_name = $params{group_name};
    my $relations = {};

    $resource->{voip_preference_groups_id} = $c->model('DB')
        ->resultset('voip_preference_groups')->find({name => $group_name})->id;
    $resource->{attribute} = dynamic_pref_attribute_to_db($resource->{attribute});
    $resource->{dynamic}   = 1;
    $resource->{internal}  = 0;
    $resource->{expose_to_customer} = 1;
    $resource->{expose_to_subscriber} = 1;

    $relations->{autoprov_device_id} = delete $resource->{autoprov_device_id};
    $relations->{reseller_id} = delete $resource->{reseller_id};

    my $enums = delete $resource->{enum};
    my $preference = $c->model('DB')->resultset('voip_preferences')->create($resource);
    my @flags = grep {$_ =~/^[a-z]+_pref$/} keys %$resource;
    if(defined $enums and ref $enums eq 'ARRAY'){
        foreach my $enum (@$enums) {
            @{$enum}{@flags} = (1) x @flags;
            $preference->create_related('voip_preferences_enums', $enum);
        }
    }

    save_dynamic_preference_relations($c, $resource, $preference, $relations);

    return $preference;
}

sub update_dynamic_preference {
    my ($c, $preference, $resource, %params) = @_;

    my $relations = {};

    $resource->{attribute} = dynamic_pref_attribute_to_db($resource->{attribute});

    $relations->{autoprov_device_id} = delete $resource->{autoprov_device_id};
    $relations->{reseller_id} = delete $resource->{reseller_id};

    my $enums = delete $resource->{enum};

    $preference->update($resource);
    my @flags = grep {$_ =~/^[a-z]+_pref$/} keys %$resource;
    if(defined $enums and ref $enums eq 'ARRAY'){
        my $enums_rs = $preference->voip_preferences_enums;
        $enums_rs->search_rs({
            id => { -not_in => [ map { $_->{id} } @$enums ] },
        })->delete;
        foreach my $enum (@$enums) {
            my $id = delete $enum->{id};
            my $enum_exists = $enums_rs->find($id);
            @{$enum}{@flags} = (1) x @flags;
            if ($enum_exists) {
                $enum_exists->update($enum);
            } else {
                $preference->create_related('voip_preferences_enums', $enum);
            }
        }
    } else {
        $preference->voip_preferences_enums->delete;
    }

    save_dynamic_preference_relations($c, $resource, $preference, $relations);

    return $preference;
}

sub delete_dynamic_preference {
    my ($c, $preference) = @_;
    $preference->voip_preferences_enums->delete;
    $preference->delete;
}

sub save_dynamic_preference_relations {
    my ($c, $resource, $preference, $relations) = @_;

    if (defined $resource->{dev_pref} && $resource->{dev_pref}) {
        if ($relations->{autoprov_device_id}) {
            $preference->search_related_rs('voip_preference_relations')->update_or_create({
                autoprov_device_id => $relations->{autoprov_device_id},
                reseller_id        => undef,
            });
        } elsif ($relations->{reseller_id}) {
            $preference->search_related_rs('voip_preference_relations')->update_or_create({
                autoprov_device_id => undef,
                reseller_id => $relations->{reseller_id},
            });
        }
    }
}

sub dynamic_pref_attribute_to_standard {
    my ($attribute) = @_;
    $attribute =~s/^_*//;
    return $attribute;
}

sub dynamic_pref_attribute_to_db {
    my ($attribute) = @_;
    $attribute =~s/^_*/_DYNAMIC_PREFERENCE_PREFIX/e;
    return $attribute;
}

sub get_blob_short_value_size {
    return $blob_short_value_size;
}

1;

=head1 NAME

NGCP::Panel::Utils::Preferences

=head1 DESCRIPTION

Various utils to outsource common tasks in the controllers
regarding voip_preferences.

=head1 METHODS

=head2 load_preference_list

Parameters:
    c - set this to $c
    pref_values - hashref with all values (from voip_x_preferences)
    peer_pref - boolean, only select peer_prefs
    dom_pref - boolean, only select dom_prefs
    usr_pref - boolean, only select dom_prefs

Load preferences and groups. Fill them with pref_values.
Put them to stash as "pref_groups". This will be used in F<helpers/pref_table.tt>.

Also see "Special case rewrite_rule_set" and "Special case ncos and adm_ncos".

=head2 create_preference_form

Parameters:
    c - set this to $c
    pref_rs - a resultset for voip_x_preferences with the specific "x" already set
    enums - arrayref of all relevant enum rows (already filtered by eg. dom_pref)
    base_uri - string, uri of the preferences list
    edit_uri - string, uri to show the preferences edit modal

Use preference and preference_meta from stash and create a form. Process that
form in case the request has be POSTed. Also parse the GET params "delete",
"activate" and "deactivate" in order to operate on maxoccur != 1 preferences.
Put the form to stash as "form".

=head3 Special case rewrite_rule_set

In order to display the preference rewrite_rule_set correctly, the calling
controller must put rwr_sets_rs (as DBIx::Class::ResultSet) and rwr_sets
(for rendering in the template) to stash. A html select will then be displayed
with all the rewrite_rule_sets. Also helper.rewrite_rule_sets needs to be
set in the template (to be used by F<helpers/pref_table.tt>).

On update 4 voip_*_preferences will be created with the attributes
rewrite_callee_in_dpid, rewrite_caller_in_dpid, rewrite_callee_out_dpid,
rewrite_caller_out_dpid, rewrite_callee_lnp_dpid, rewrite_caller_lnp_dpid
(using the helper method set_rewrite_preferences).

For compatibility with ossbss and the www_admin panel, no preference with
the attribute rewrite_rule_set is created and caller_in_dpid is used to
check which rewrite_rule_set is currently set.

=head3 Special case ncos and adm_ncos

Very similar to rewrite_rule_set (see above). The stashed variables are
ncos_levels_rs and ncos_levels. In the template helper.ncos_levels needs to
be set.

The updated preferences are called ncos_id and adm_ncos_id.

=head3 Special case emergency_mapping_container

Very similar to ncos (see above). The stashed variables are
emergency_mapping_containers_rs and emergency_mapping_containers. In the template
helper.ncos_levels needs to be set.

The updated preferences are called ncos_id and adm_ncos_id.

=head3 Special case sound_set and contract_sound_set

This is also similar to rewrite_rule_set and ncos. The stashed variables are
(contract_)sound_sets_rs and (contract_)sound_sets. In the template helper.(contract_)sound_sets needs to
be set.

The preference with the attribute (contract_)sound_set will contain the id of a sound set.

=head3 Special case allowed_ips

Also something special here. The table containing data is
provisioning.voip_allowed_ip_groups.

=head2 set_rewrite_preferences

See "Special case rewrite_rule_set".

=head1 AUTHOR

Andreas Granig,
Gerhard Jungwirth

=head1 LICENSE

This library is free software. You can redistribute it and/or modify
it under the same terms as Perl itself.

=cut
# vim: set tabstop=4 expandtab:
