#!/usr/bin/perl

use strict;
use warnings;

use Getopt::Long qw(:config posix_default no_ignorecase);
use Pod::Usage qw(pod2usage);
use Config::General qw();
use File::Slurp qw();
use XML::Simple qw();

#use lib "/home/rkrenn/sipwise/git/ngcp-schema/lib";
#use lib "/home/rkrenn/sipwise/git/sipwise-base/lib";
#use lib "/usr/local/devel/ngcp-schema/lib";
#use lib "/usr/local/devel/sipwise-base/lib";
use NGCP::Schema qw();
#use lib "/home/rkrenn/sipwise/git/ngcp-panel/lib";
#use lib "/usr/local/devel/ngcp-panel/lib";
use NGCP::Panel::Utils::ProvisioningTemplates qw();

my @panel_configs = qw(
   /etc/ngcp-panel/ngcp_panel.conf
   /etc/ngcp_panel.conf
   /ngcp_panel.conf
);
#/home/rkrenn/sipwise/git/vagrant-ngcp/ngcp_panel.conf
#/usr/local/devel/vagrant-ngcp/ngcp_panel.conf

my @provisioning_configs = qw(
   /etc/ngcp-panel/provisioning.conf
);
#/etc/ngcp-panel/provisioning.conf
#/home/rkrenn/sipwise/git/vagrant-ngcp/provisioning.conf
#/usr/local/devel/vagrant-ngcp/provisioning.conf

my %db = (
    host => undef, #'192.168.0.99'
    port => 3306,
    user => 'root',
    password => undef,
);

my $template_name = shift @ARGV;
#my $template_name = 'My First Provisioning Template'; 
pod2usage(2) unless $template_name;
pod2usage(1) if $template_name =~ /help|\?/;
my $help;
my $log_level;

Getopt::Long::Configure("pass_through");
Getopt::Long::Configure("permute");
GetOptions(
    ( map { ('db_' . $_ . ':s') => \&parse_old_db_option } keys %db ),
    ( map { ('db-' . $_ . ':s') => \$db{$_}; } keys %db ),
    'log_level:s' => \&parse_old_log_level_option,
    'log-level:s' => \$log_level,
    "help|?" => \$help,
) or pod2usage(2);
pod2usage(1) if $help;
my %mock_objs = ();
my $c = _create_c();
NGCP::Panel::Utils::ProvisioningTemplates::load_template_map($c);
if (exists $c->stash->{'provisioning_templates'}->{$template_name}) {
    $c->stash->{'provisioning_template_name'} = $template_name;

    my $fields = NGCP::Panel::Utils::ProvisioningTemplates::get_fields($c,0);
    my $values = { map { $_ => undef; } grep { $_ ne 'purge'; } keys %$fields };
    my $csv_file;
    #my $csv_file = '/home/rkrenn/temp/provisioning_templates/CCS_ICM_Nummern.csv';
    my $purge;
    Getopt::Long::Configure("no_pass_through");
    Getopt::Long::Configure("no_permute");
    GetOptions(
        ( map { ($_ . ($fields->{$_}->{required} ? '=' : ':') . 's') => \$values->{$_}; } keys %$values ),
        "purge" => \$purge,
        "file:s" => \$csv_file,
    ) or pod2usage(2);

    if ($csv_file) {
        my $csv_data = File::Slurp::read_file($csv_file);
        my ($linecount,$errors) = NGCP::Panel::Utils::ProvisioningTemplates::process_csv(
            c => $c,
            data => \$csv_data,
            purge => $purge,
        );
        if (scalar @$errors) {
            die("CSV file ($linecount lines) processed, " . scalar @$errors . " error(s)\n");
        } else {
            print("CSV file ($linecount lines) processed, 0 error(s)\n");
        }
    } else {
        $values->{purge} = $purge;
        eval {
            my $context = NGCP::Panel::Utils::ProvisioningTemplates::provision_begin(
                c => $c,
            );
            NGCP::Panel::Utils::ProvisioningTemplates::provision_commit_row(
                c => $c,
                context => $context,
                'values' => $values,
                #'values' => {
                #    first_name => 'John',
                #    last_name => 'Doe',
                #    cc => "43",
                #    ac => "316",
                #    sn => "1234567",
                #    purge => 1,
                #}
                #'values' => {
                #    service_number => '800010144',
                #    switch3_number => '19768988',
                #    company => "test12",
                #    purge => 1,
                #}
            );
            NGCP::Panel::Utils::ProvisioningTemplates::provision_finish(
                c => $c,
                context => $context,
            );
            print ("Provisioning template '$template_name' done: subscriber " . $context->{subscriber}->{username} . '@' . $context->{domain}->{domain} . " created\n");
        };
        if ($@) {
            die("Provisioning template '$template_name' failed: " . $@ . "\n");
        }
    }
} else {
    die("Unknown provisioning template '$template_name'\n");
}

exit(0);

sub parse_old_db_option {
    my ($name, $value) = @_;
    my $newname = $name =~ tr/_/-/r;
    $db{$name} = $value;
    #warn "$0: option --$name is deprecated; use --$newname instead\n";
}

sub parse_old_log_level_option {
    my ($name, $value) = @_;
    my $newname = $name =~ tr/_/-/r;
    $log_level = $value;
    #warn "$0: option --$name is deprecated; use --$newname instead\n";
}

sub _create_c {

    my $config = _get_panel_config();

    {
        no strict "refs";  ## no critic (ProhibitNoStrict)
        *{'NGCP::Schema::set_transaction_isolation'} = sub {
            my ($self,$level) = @_;
            return $self->storage->dbh_do(
                sub {
                  my ($storage, $dbh, @args) = @_;
                  $dbh->do("SET TRANSACTION ISOLATION LEVEL " . $args[0]);
                },
                $level,
            );
        };
        *{'NGCP::Schema::set_wait_timeout'} = sub {
            my ($self,$timeout) = @_;
            return $self->storage->dbh_do(
                sub {
                  my ($storage, $dbh, @args) = @_;
                  $dbh->do("SET SESSION wait_timeout = " . $args[0]);
                },
                $timeout,
            );
        };
    }
    my $db = _get_schema();

    my $req = sub {
            my $self = shift;
            return _create_mock('_request',
                _param => {
                #    '$c->request->params' => undef,
                },
                param => sub {
                    my $self = shift;
                    my $key = shift;
                    return $self->{param}->{$key} if $key;
                    return $self->{param};
                },
                params => sub {
                    my $self = shift;
                    my $key = shift;
                    return $self->{param}->{$key} if $key;
                    return $self->{param};
                },
                path => sub {
                    my $self = shift;
                    return 'api/';
                },
                method => sub {
                    my $self = shift;
                    return ''; #disable license checks (max_subscribers) for now
                },
            );
        };

    $c = _create_mock('c',
        _stash => {

        },
        stash => sub {
            my $self = shift;
            my $key = shift;
            return $self->{stash}->{$key} if $key;
            return $self->{stash};
        },
        _model => {
            DB => $db,
        },
        model => sub {
            my $self = shift;
            my $key = shift;
            return $self->{model}->{$key} if $key;
            return $self->{model};
        },
        log => sub {
            my $self = shift;
            return _create_mock('log',
                debug => sub {
                    my $self = shift;
                    my $str = shift;
                    my @params = @_;
                    print $str . "\n" if ($log_level and grep { $_ eq lc($log_level); } qw(debug));
                },
                info => sub {
                    my $self = shift;
                    my $str = shift;
                    my @params = @_;
                    print $str . "\n" if ($log_level and grep { $_ eq lc($log_level); } qw(info debug));
                },
                warn => sub {
                    my $self = shift;
                    my $str = shift;
                    my @params = @_;
                    print $str . "\n" if ($log_level and grep { $_ eq lc($log_level); } qw(warn info debug));
                },
                error => sub {
                    my $self = shift;
                    my $str = shift;
                    my @params = @_;
                    print $str . "\n" if ($log_level and grep { $_ eq lc($log_level); } qw(error warn info debug));
                },
            );
        },
        config => sub {
            my $self = shift;
            return $config;
        },
        user => sub {
            my $self = shift;
            return _create_mock('user',
                roles => sub {
                    my $self = shift;
                    return 'admin';
                },
                #webusername => sub {
                #    my $self = shift;
                #    return '$c->user->webusername';
                #},
                #domain => sub {
                #    my $self = shift;
                #    return _create_mock('_domain',
                #        domain => sub {
                #            my $self = shift;
                #            return '$c->user->domain->domain';
                #        },
                #    );
                #},
            );
        },
        request => $req,
        req => $req,

    );

}

sub _create_mock {
    my $class = shift;
    return $mock_objs{$class} if exists $mock_objs{$class};
    my %members = @_;
    my $obj = bless({},$class);
    foreach my $member (keys %members) {
        if ('CODE' eq ref $members{$member}) {
            no strict "refs";  ## no critic (ProhibitNoStrict)
            *{$class.'::'.$member} = $members{$member};
        } else {
            $obj->{substr($member,1)} = $members{$member};
        }
    }
    $mock_objs{$class} = $obj;
    return $obj;
}

sub _get_schema {

    my $schema;
    if ($db{host}) {
        $schema = NGCP::Schema->connect({
            dsn                 => "DBI:mysql:database=provisioning;host=$db{host};port=$db{port}",
            user                => $db{user},
            ( exists $db{password} ? (password => $db{password}) : ()),
            mysql_enable_utf8   => "1",
            on_connect_do       => "SET NAMES utf8mb4",
            quote_char          => "`",
        });
        $schema->set_wait_timeout(3600);
        return $schema;
    } else {
        foreach my $provisioning_config (@provisioning_configs) {
            if (-f $provisioning_config) {
                my $conf = XML::Simple->new->XMLin($provisioning_config, ForceArray => 1);
                if ($conf && $conf->{ngcp_connect_info}) {
                    my $connectors;
                    if (exists $conf->{ngcp_connect_info}->[0]->{connectors}) {
                         $connectors = $conf->{ngcp_connect_info}->[0]->{connectors};
                    } else {
                        $connectors = $conf->{ngcp_connect_info};
                    }
                    $connectors //= [];
                    $schema = NGCP::Schema->connect($connectors->[0]);
                    $schema->set_wait_timeout(3600);
                    return $schema;
                }
            }
        }
        die("no provisioning.conf found\n") unless $schema;
    }

}

sub _get_panel_config {

    my $config;
    foreach my $panel_config (@panel_configs) {
        if( -f $panel_config ){
            my $catalyst_config = Config::General->new($panel_config);
            my %config = $catalyst_config->getall();
            $config = \%config;
            last;
        }
    }
    die("no ngcp_panel.conf found\n") unless $config;
    return $config;

}

__END__

=head1 NAME

ngcp-provisioning-template - Create subscribers with detailed settings according to provisioning templates

=head1 SYNOPSIS

For templates defined in config.yml file:

E<9>B<ngcp-provisioning-template> "I<provisioning-template-name>" [I<options>]

For templates defined in database:

E<9>B<ngcp-provisioning-template> "I<reseller-name>B</>I<provisioning-template-name>" [I<options>]

=head1 DESCRIPTION

This program allows to run a 'provisioning template' from database or config.yml. This will produce a
subscriber setup including required billing contact, contract, preferences, etc. from an input form defined
by that template. The form fields can be passed as command line options.

=head1 OPTIONS

=over 4

=item B<--help>

Print a brief help message and exits.

=item B<--db-host=>I<db-host-IP-address>

The host of the ngcp database to connect to. If omitted, the database connection settings of ngcp-panel will be used.

=item B<--db-port=>I<db-host-port>

The port of the ngcp database to connect to.
Only relevant if B<--db-host> is specified.

=item B<--db-user=>I<db-username>

The database user for the ngcp database to connect to.
Only relevant if B<--db-host> is specified.

=item B<--db-password=>I<db-password>

The database user password (if any) for the ngcp database to connect to.
Only relevant if B<--db-host> is specified.

=item B<--file=>I<csv-filename>

Specify a .csv file to process. Each row represents form values for one subscriber to create.

=item B<--[input-attribute-name]=>I<[attribute-value]>

Provide an input attribute name and its value, as defined within the 'fields' tag in the provisioning template (Attribute must not be internal, i.e. 'calculated' type). Only relevant if no --file is specified.

=item B<--purge>

Terminate an existing subscriber with duplicate number/aliases first.

=item B<--log-level>

Verbosity of printed messages while processing (debug, info, warn, error).

=back

=head1 EXAMPLES

ngcp-provisioning-template "My First Provisioning Template" --first-name="John" --last-name="Doe" --cc="43" --ac="316" --sn="123456" --purge

... runs "My First Provisioning Template" from config.yml using first_name "John", last_name "Doe" etc.

ngcp-provisioning-template "Reseller1/Provisioning Template 1" --file="subscriberdata.csv" --purge

... runs "Provisioning Template 1" of "Reseller1" for each row in "subscriberdata.csv"

=head1 AUTHOR

Sipwise Development Team C<< <support@sipwise.com> >>

=head1 LICENSE

This software is Copyright © 2020 by Sipwise GmbH, Austria.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this package.  If not, see <https://www.gnu.org/licenses/>.

=cut
