package NGCP::Panel::Utils::Events;

use Sipwise::Base;

use NGCP::Panel::Utils::DateTime qw();

use constant ENABLE_EVENTS => 1;
use constant CREATE_EVENT_PER_ALIAS => 1;
#use end+start profile events instead of update event when moving numbers between pilot and extension susbcribers:
use constant MOVED_UPDATE_PROFILE_EVENT => 0;

sub insert_deferred {
    my %params = @_;
    my $c = $params{c};
    my $schema = $params{schema} // $c->model('DB');
    my $events_to_create = $params{events_to_create} // [];
    my $inserted = 0;
    while (my $event = shift @$events_to_create) {
        if ('profile' eq $event->{type}) {
            $inserted += insert_profile_events(c => $c, schema => $schema,
                %$event
            );
        } else {
            $inserted += insert(c => $c, schema => $schema,
                %$event
            );
        }
    }
    return $inserted;
}

sub insert_profile_events {
    my %params = @_;

    return 0 unless ENABLE_EVENTS;

    my $c = $params{c};
    my $schema = $params{schema} // $c->model('DB');
    #my $type = $params{type};
    #my $subscriber = $params{subscriber};
    my $old_profile_id = $params{old};
    my $new_profile_id = $params{new};

    my $now_hires = NGCP::Panel::Utils::DateTime::current_local_hires;
    #reload it usually:
    my $subscriber = $params{subscriber} // $schema->resultset('voip_subscribers')->find({
        id => $params{subscriber_id},
    });

    my $prov_subscriber = $subscriber->provisioning_voip_subscriber;
    my $is_pilot = ($prov_subscriber && $prov_subscriber->is_pbx_pilot ? 1 : 0);
    my $pilot_subscriber = _get_pilot_subscriber(
        c => $c,
        schema => $schema,
        subscriber => $subscriber,
        prov_subscriber => $prov_subscriber,
        now_hires => $now_hires,
    );
    my $pilot_prov_subscriber;
    $pilot_prov_subscriber = $pilot_subscriber->provisioning_voip_subscriber if $pilot_subscriber;

    my ($old_aliases,$old_aliases_map) = _get_aliases_map($params{old_aliases},$prov_subscriber);
    my ($old_pilot_aliases,$old_pilot_aliases_map) = _get_aliases_map($params{old_pilot_aliases},$pilot_prov_subscriber);
    my ($new_aliases,$new_aliases_map) = _get_aliases_map($params{new_aliases},$prov_subscriber);
    my ($new_pilot_aliases,$new_pilot_aliases_map) = _get_aliases_map($params{new_pilot_aliases},$pilot_prov_subscriber);

    my $context = { c => $c, schema => $schema,
        subscriber => $subscriber,
        old_aliases => $params{old_aliases},
        new_aliases => $params{new_aliases},
        old_pilot_aliases => $params{old_pilot_aliases},
        new_pilot_aliases => $params{new_pilot_aliases},
        old_primary_alias => $params{old_primary_alias},
        new_primary_alias => $params{new_primary_alias},
        old_pilot_primary_alias => $params{old_pilot_primary_alias},
        new_pilot_primary_alias => $params{new_pilot_primary_alias},
        create_event_per_alias => 0,
        now_hires => $now_hires};

    my $inserted = 0;
    foreach my $new_alias (@$new_aliases) {
        $context->{alias} = $new_alias;
        if (not exists $old_aliases_map->{$new_alias}) {
            if (not $is_pilot and exists $old_pilot_aliases_map->{$new_alias}) {
                #aliases moved from pilot to subs
                if ($pilot_prov_subscriber) {
                    $context->{old} = $pilot_prov_subscriber->profile_id;
                    $context->{new} = $new_profile_id;
                    $inserted += _insert_profile_event($context,MOVED_UPDATE_PROFILE_EVENT);
                }
            } else {
                #aliases added
                $context->{old} = undef;
                $context->{new} = $new_profile_id;
                $inserted += _insert_profile_event($context,1);
            }
        } else {
            #no number change
            $context->{old} = $old_profile_id;
            $context->{new} = $new_profile_id;
            $inserted += _insert_profile_event($context,1);
        }
    }
    foreach my $old_alias (@$old_aliases) {
        $context->{alias} = $old_alias;
        if (not exists $new_aliases_map->{$old_alias}) {
            if (not $is_pilot and exists $new_pilot_aliases_map->{$old_alias}) {
                #aliases moved from subs to pilot
                if ($pilot_prov_subscriber) {
                    $context->{old} = $old_profile_id;
                    $context->{new} = $pilot_prov_subscriber->profile_id;
                    $inserted += _insert_profile_event($context,MOVED_UPDATE_PROFILE_EVENT);
                }
            } else {
                #aliases deleted
                $context->{old} = $old_profile_id;
                $context->{new} = undef;
                $inserted += _insert_profile_event($context,1);
            }
        } else {
            #no number change
        }
    }
    if ((scalar @$old_aliases) + (scalar @$new_aliases) == 0) {
        $context->{alias} = undef;
        $context->{old} = $old_profile_id;
        $context->{new} = $new_profile_id;
        $context->{create_event_per_alias} = undef;
        $inserted += _insert_profile_event($context,1);
    }

}

sub _insert_profile_event {

    my ($context,$update_profile_event) = @_;
    my $inserted = 0;
    if(($context->{old} // 0) != ($context->{new} // 0)) {
        if(defined $context->{old} && defined $context->{new}) {
            if ($update_profile_event) {
                $context->{type} = "update_profile";
                $inserted += insert(%$context);
            } else {
                $context->{type} = "end_profile";
                my $new = $context->{new};
                undef $context->{new};
                $inserted += insert(%$context);
                $context->{type} = "start_profile";
                $context->{new} = $new;
                my $old = $context->{old};
                undef $context->{old};
                $inserted += insert(%$context);
                $context->{old} = $old;
                $context->{type} = "update_profile";
            }
        } else {
            if(defined $context->{new}) {
                $context->{type} = "start_profile";
            } else {
                $context->{type} = "end_profile";
            }
            $inserted += insert(%$context);
        }
    }
    return $inserted;

}

sub insert {
    my %params = @_;

    return 0 unless ENABLE_EVENTS;

    my $c = $params{c};
    my $schema = $params{schema} // $c->model('DB');
    my $type = $params{type};
    #my $subscriber = $params{subscriber};
    my $old = $params{old};
    my $new = $params{new};
    my $old_aliases = $params{old_aliases};
    my $old_pilot_aliases = $params{old_pilot_aliases};
    my $new_aliases = $params{new_aliases};
    my $new_pilot_aliases = $params{new_pilot_aliases};
    my $old_primary_alias = $params{old_primary_alias};
    my $new_primary_alias = $params{new_primary_alias};
    my $old_pilot_primary_alias = $params{old_pilot_primary_alias};
    my $new_pilot_primary_alias = $params{new_pilot_primary_alias};
    my $create_event_per_alias = $params{create_event_per_alias} // CREATE_EVENT_PER_ALIAS;
    my $event_alias = $params{alias};
    my $now_hires = $params{now_hires} // NGCP::Panel::Utils::DateTime::current_local_hires;

    #reload it usually:
    my $subscriber = $params{subscriber} // $schema->resultset('voip_subscribers')->find({
        id => $params{subscriber_id},
    });

    my $customer = $subscriber->contract;
    my $prov_subscriber = $subscriber->provisioning_voip_subscriber;
    my $pilot_subscriber = $params{pilot_subscriber} // _get_pilot_subscriber(
        c => $c,
        schema => $schema,
        subscriber => $subscriber,
        customer => $customer,
        prov_subscriber => $prov_subscriber,
        now_hires => $now_hires,
    );

    my $tags_rs = $schema->resultset('events_tag');
    my $relations_rs = $schema->resultset('events_relation');

    my @aliases = ();
    if ($prov_subscriber) {
        if ($create_event_per_alias) { #create events for all aliases
            foreach my $alias (_get_aliases_sorted_rs($prov_subscriber)->all) {
                push(@aliases,$alias->username);
            }
        } elsif (defined $event_alias) { #create event for a specific alias
            push(@aliases,$event_alias);
        }
        unless ((scalar @aliases) > 0) { #create event for no alias
            push(@aliases,undef);
        }
    }

    my $inserted = 0;
    foreach my $alias_username (@aliases) {
        my $event = $schema->resultset('events')->create({
            type => $type,
            subscriber_id => $subscriber->id,
            reseller_id => $customer->contact->reseller_id,
            old_status => $old // '',
            new_status => $new // '',
            timestamp => $now_hires,
            export_status => 'unexported',
            exported_at => undef,
        });

        _save_voip_number(
            schema => $schema,
            event => $event,
            number => $subscriber->primary_number,
            types_prefix => 'primary_number_',
            now_hires => $now_hires,
            tags_rs => $tags_rs,
            relations_rs => $relations_rs,
        );

        _save_subscriber_profile(
            schema => $schema,
            event => $event,
            subscriber_profile => ($prov_subscriber ? $prov_subscriber->voip_subscriber_profile : undef),
            types_prefix => 'subscriber_profile_',
            now_hires => $now_hires,
            tags_rs => $tags_rs,
            relations_rs => $relations_rs,
        );

        _save_subscriber_profile_set(
            schema => $schema,
            event => $event,
            subscriber_profile_set => ($prov_subscriber ? $prov_subscriber->voip_subscriber_profile_set : undef),
            types_prefix => 'subscriber_profile_set_',
            now_hires => $now_hires,
            tags_rs => $tags_rs,
            relations_rs => $relations_rs,
        );

        _save_first_non_primary_alias(
            schema => $schema,
            event => $event,
            (defined $old_aliases ? (aliases => $old_aliases) : (prov_subscriber => $prov_subscriber)),
            types_prefix => '',
            types_suffix => '_before',
            now_hires => $now_hires,
            tags_rs => $tags_rs,
        );
        _save_first_non_primary_alias(
            schema => $schema,
            event => $event,
            (defined $new_aliases ? (aliases => $new_aliases) : (prov_subscriber => $prov_subscriber)),
            types_prefix => '',
            types_suffix => '_after',
            now_hires => $now_hires,
            tags_rs => $tags_rs,
        );

        _save_primary_alias(
            schema => $schema,
            event => $event,
            (defined $old_primary_alias ? (alias => $old_primary_alias) : (prov_subscriber => $prov_subscriber)),
            types_prefix => '',
            types_suffix => '_before',
            now_hires => $now_hires,
            tags_rs => $tags_rs,
        );
        _save_primary_alias(
            schema => $schema,
            event => $event,
            (defined $new_primary_alias ? (alias => $new_primary_alias) : (prov_subscriber => $prov_subscriber)),
            types_prefix => '',
            types_suffix => '_after',
            now_hires => $now_hires,
            tags_rs => $tags_rs,
        );

        _save_alias(
            schema => $schema,
            event => $event,
            alias_username => $alias_username,
            now_hires => $now_hires,
            tags_rs => $tags_rs,
        );

        if ($pilot_subscriber) {
            $event->create_related("relation_data", {
                relation_id => $relations_rs->find({ type => 'pilot_subscriber_id' })->id,
                val => $pilot_subscriber->id,
                event_timestamp => $now_hires,
            });
            my $pilot_prov_subscriber = $pilot_subscriber->provisioning_voip_subscriber;
            _save_voip_number(
                schema => $schema,
                event => $event,
                number => $pilot_subscriber->primary_number,
                types_prefix => 'pilot_primary_number_',
                now_hires => $now_hires,
                tags_rs => $tags_rs,
                relations_rs => $relations_rs,
            );
            _save_subscriber_profile(
                schema => $schema,
                event => $event,
                subscriber_profile => ($pilot_prov_subscriber ? $pilot_prov_subscriber->voip_subscriber_profile : undef),
                types_prefix => 'pilot_subscriber_profile_',
                now_hires => $now_hires,
                tags_rs => $tags_rs,
                relations_rs => $relations_rs,
            );

            _save_subscriber_profile_set(
                schema => $schema,
                event => $event,
                subscriber_profile_set => ($pilot_prov_subscriber ? $pilot_prov_subscriber->voip_subscriber_profile_set : undef),
                types_prefix => 'pilot_subscriber_profile_set_',
                now_hires => $now_hires,
                tags_rs => $tags_rs,
                relations_rs => $relations_rs,
            );

            _save_first_non_primary_alias(
                schema => $schema,
                event => $event,
                (defined $old_pilot_aliases ? (aliases => $old_pilot_aliases) : (prov_subscriber => $pilot_prov_subscriber)),
                types_prefix => 'pilot_',
                types_suffix => '_before',
                now_hires => $now_hires,
                tags_rs => $tags_rs,
            );
            _save_first_non_primary_alias(
                schema => $schema,
                event => $event,
                (defined $new_pilot_aliases ? (aliases => $new_pilot_aliases) : (prov_subscriber => $pilot_prov_subscriber)),
                types_prefix => 'pilot_',
                types_suffix => '_after',
                now_hires => $now_hires,
                tags_rs => $tags_rs,
            );

            _save_primary_alias(
                schema => $schema,
                event => $event,
                (defined $old_pilot_primary_alias ? (alias => $old_pilot_primary_alias) : (prov_subscriber => $pilot_prov_subscriber)),
                types_prefix => 'pilot_',
                types_suffix => '_before',
                now_hires => $now_hires,
                tags_rs => $tags_rs,
            );
            _save_primary_alias(
                schema => $schema,
                event => $event,
                (defined $new_pilot_primary_alias ? (alias => $new_pilot_primary_alias) : (prov_subscriber => $pilot_prov_subscriber)),
                types_prefix => 'pilot_',
                types_suffix => '_after',
                now_hires => $now_hires,
                tags_rs => $tags_rs,
            );
        }
        $inserted++;
        $c->log->debug('edr event "'. $type .'" inserted: subscriber id ' . $subscriber->id . ($alias_username ? ", alias $alias_username" : ''));
    }
    return $inserted;
}

sub _get_pilot_subscriber {
    my %params = @_;
    my ($c,
        $schema,
        $subscriber,
        $customer,
        $prov_subscriber,
        $now_hires,
        ) = @params{qw/
        c
        schema
        subscriber
        customer
        prov_subscriber
        now_hires
    /};
    $schema //= $c->model('DB');
    $customer //= $subscriber->contract;
    $prov_subscriber //= $subscriber->provisioning_voip_subscriber;
    my $pilot_subscriber = undef;
    if ($prov_subscriber and $prov_subscriber->is_pbx_pilot) {
        $pilot_subscriber = $subscriber;
    } else {
        $pilot_subscriber = $customer->voip_subscribers->search({
            'provisioning_voip_subscriber.is_pbx_pilot' => 1,
        },{
            join => 'provisioning_voip_subscriber',
        })->first;
    }
    return $pilot_subscriber;
}

sub _save_voip_number {
    my %params = @_;
    my ($schema,
        $event,
        $number,
        $types_prefix,
        $now_hires,
        $tags_rs,
        $relations_rs) = @params{qw/
        schema
        event
        number
        types_prefix
        now_hires
        tags_rs
        relations_rs
    /};
    if ($number) {
        $tags_rs //= $schema->resultset('events_tag');
        $relations_rs //= $schema->resultset('events_relation');
        $event->create_related("relation_data", {
            relation_id => $relations_rs->find({ type => $types_prefix.'id' })->id,
            val => $number->id,
            event_timestamp => $now_hires,
        });
        if (length(my $cc = $number->cc) > 0) {
            $event->create_related("tag_data", {
                tag_id => $tags_rs->find({ type => $types_prefix.'cc' })->id,
                val => $cc,
                event_timestamp => $now_hires,
            });
        }
        if (length(my $ac = $number->ac) > 0) {
            $event->create_related("tag_data", {
                tag_id => $tags_rs->find({ type => $types_prefix.'ac' })->id,
                val => $ac,
                event_timestamp => $now_hires,
            });
        }
        if (length(my $sn = $number->sn) > 0) {
            $event->create_related("tag_data", {
                tag_id => $tags_rs->find({ type => $types_prefix.'sn' })->id,
                val => $sn,
                event_timestamp => $now_hires,
            });
        }
    }
}

sub _save_subscriber_profile {
    my %params = @_;
    my ($schema,
        $event,
        $subscriber_profile,
        $types_prefix,
        $now_hires,
        $tags_rs,
        $relations_rs) = @params{qw/
        schema
        event
        subscriber_profile
        types_prefix
        now_hires
        tags_rs
        relations_rs
    /};

    if ($subscriber_profile) {
        $tags_rs //= $schema->resultset('events_tag');
        $relations_rs //= $schema->resultset('events_relation');
        $event->create_related("relation_data", {
            relation_id => $relations_rs->find({ type => $types_prefix.'id' })->id,
            val => $subscriber_profile->id,
            event_timestamp => $now_hires,
        });
        $event->create_related("tag_data", {
            tag_id => $tags_rs->find({ type => $types_prefix.'name' })->id,
            val => $subscriber_profile->name,
            event_timestamp => $now_hires,
        });
    }
}

sub _save_subscriber_profile_set {
    my %params = @_;
    my ($schema,
        $event,
        $subscriber_profile_set,
        $types_prefix,
        $now_hires,
        $tags_rs,
        $relations_rs) = @params{qw/
        schema
        event
        subscriber_profile_set
        types_prefix
        now_hires
        tags_rs
        relations_rs
    /};

    if ($subscriber_profile_set) {
        $tags_rs //= $schema->resultset('events_tag');
        $relations_rs //= $schema->resultset('events_relation');
        $event->create_related("relation_data", {
            relation_id => $relations_rs->find({ type => $types_prefix.'id' })->id,
            val => $subscriber_profile_set->id,
            event_timestamp => $now_hires,
        });
        $event->create_related("tag_data", {
            tag_id => $tags_rs->find({ type => $types_prefix.'name' })->id,
            val => $subscriber_profile_set->name,
            event_timestamp => $now_hires,
        });
    }
}

sub _save_first_non_primary_alias {
    my %params = @_;
    my ($schema,
        $event,
        $aliases,
        $prov_subscriber,
        $types_prefix,
        $types_suffix,
        $now_hires,
        $tags_rs) = @params{qw/
        schema
        event
        aliases
        prov_subscriber
        types_prefix
        types_suffix
        now_hires
        tags_rs
    /};
    my $alias_username = undef;
    if ($aliases) {
        #my $alias = shift(sort { $a->{is_primary} <=> $b->{is_primary} || $a->{id} <=> $b->{id}; } @$aliases);
        my $alias = $aliases->[0]; #expect the ary sorted
        $alias_username = $alias->{username} if $alias;
    } elsif ($prov_subscriber) {
        my $alias = _get_aliases_sorted_rs($prov_subscriber)->first;
        $alias_username = $alias->username if $alias;
    }
    if ($alias_username) {
        $tags_rs //= $schema->resultset('events_tag');
        $event->create_related("tag_data", {
            tag_id => $tags_rs->find({ type => $types_prefix.'first_non_primary_alias_username'.$types_suffix })->id,
            val => $alias_username,
            event_timestamp => $now_hires,
        });
    }

}

sub _save_primary_alias {
    my %params = @_;
    my ($schema,
        $event,
        $alias,
        $prov_subscriber,
        $types_prefix,
        $types_suffix,
        $now_hires,
        $tags_rs) = @params{qw/
        schema
        event
        alias
        prov_subscriber
        types_prefix
        types_suffix
        now_hires
        tags_rs
    /};
    my $alias_username = undef;
    if ($alias) {
        $alias_username = $alias->{username} if $alias;
    } elsif ($prov_subscriber) {
        my $alias = _get_primary_alias($prov_subscriber);
        $alias_username = $alias->username if $alias;
    }
    if ($alias_username) {
        $tags_rs //= $schema->resultset('events_tag');
        $event->create_related("tag_data", {
            tag_id => $tags_rs->find({ type => $types_prefix.'primary_alias_username'.$types_suffix })->id,
            val => $alias_username,
            event_timestamp => $now_hires,
        });
    }

}

sub _save_alias {
    my %params = @_;
    my ($schema,
        $event,
        $alias_username,
        $now_hires,
        $tags_rs) = @params{qw/
        schema
        event
        alias_username
        now_hires
        tags_rs
    /};
    if ($alias_username) {
        $tags_rs //= $schema->resultset('events_tag');
        $event->create_related("tag_data", {
            tag_id => $tags_rs->find({ type => 'non_primary_alias_username' })->id,
            val => $alias_username,
            event_timestamp => $now_hires,
        });
    }
}

sub _get_aliases_sorted_rs {
    my ($prov_subscriber) = @_;
    return $prov_subscriber->voip_dbaliases->search_rs({
        is_primary => 0,
    },{
        #previously in ngcpcfg: #order_by => { -asc => ['is_primary', 'id'] },
        order_by => { -asc => 'id' },
    });
}

sub _get_primary_alias {
    my ($prov_subscriber) = @_;
    return $prov_subscriber->voip_dbaliases->search_rs({
        is_primary => 1,
    },undef)->first;
}

sub _get_aliases_map {
    my ($aliases, $prov_subscriber) = @_;
    my @alias_usernames = ();
    my %alias_map = ();
    if ('ARRAY' eq ref $aliases) {
        foreach my $alias (@$aliases) {
            $alias_map{$alias->{username}} = $alias;
            push(@alias_usernames,$alias->{username});
        }
    } else {
        if ($prov_subscriber) {
            foreach my $alias (_get_aliases_sorted_rs($prov_subscriber)->all) {
                $alias_map{$alias->username} = { $alias->get_inflated_columns };
                push(@alias_usernames,$alias->username);
            }
        }
    }
    return (\@alias_usernames,\%alias_map);
}

sub get_aliases_snapshot {
    my %params = @_;
    my $c = $params{c};
    my $schema = $params{schema} // $c->model('DB');
    my @aliases = ();
    my $primary_alias = undef;
    my @pilot_aliases = ();
    my $pilot_primary_alias = undef;
    if (ENABLE_EVENTS) {
        my $subscriber = $params{subscriber} // $schema->resultset('voip_subscribers')->find({
            id => $params{subscriber_id},
        });
        unless ($subscriber) {
            goto done;
        }
        my $prov_subscriber = $subscriber->provisioning_voip_subscriber;
        if ($prov_subscriber) {
            foreach my $alias (_get_aliases_sorted_rs($prov_subscriber)->all) {
                push(@aliases,{ $alias->get_inflated_columns });
            }
            $primary_alias = _get_primary_alias($prov_subscriber);
            $primary_alias = { $primary_alias->get_inflated_columns } if $primary_alias;
        }
        my $pilot_subscriber = _get_pilot_subscriber(
            c => $c,
            schema => $schema,
            subscriber => $subscriber,
            prov_subscriber => $prov_subscriber,
        );
        if ($pilot_subscriber) {
            my $pilot_prov_subscriber = $pilot_subscriber->provisioning_voip_subscriber;
            foreach my $alias (_get_aliases_sorted_rs($pilot_prov_subscriber)->all) {
                push(@pilot_aliases,{ $alias->get_inflated_columns });
            }
            $pilot_primary_alias = _get_primary_alias($pilot_prov_subscriber);
            $pilot_primary_alias = { $pilot_primary_alias->get_inflated_columns } if $pilot_primary_alias;
        }
    }
done:
    return { old_aliases => \@aliases, old_pilot_aliases => \@pilot_aliases,
        old_primary_alias => $primary_alias, old_pilot_primary_alias => $pilot_primary_alias };
}

sub get_relation_value {
    my %params = @_;
    my ($c,$schema,$event,$type) = @params{qw/c schema event type/};
    $schema //= $c->model('DB');
    if ($event) {
        my $relations_rs = $schema->resultset('events_relation');
        my $relation_data = $event->relation_data->find({
            relation_id => $relations_rs->find({ type => $type })->id,
        });
        if ($relation_data) {
            return $relation_data->val;
        }
    }
    return;
}

sub get_tag_value {
    my %params = @_;
    my ($c,$schema,$event,$type) = @params{qw/c schema event type/};
    $schema //= $c->model('DB');
    if ($event) {
        my $tags_rs = $schema->resultset('events_tag');
        my $tag_data = $event->tag_data->find({
            tag_id => $tags_rs->find({ type => $type })->id,
        });
        if ($tag_data) {
            return $tag_data->val;
        }
    }
    return;
}

1;
