package NGCP::Panel;

use Moose;
use Catalyst::Runtime 5.80;
use File::Slurp qw();
use Config::General qw();
use IO::Socket::UNIX qw(SOCK_DGRAM);

# Set flags and add plugins for the application.
#
# Note that ORDERING IS IMPORTANT here as plugins are initialized in order,
# therefore you almost certainly want to keep ConfigLoader at the head of the
# list if you're using it.
#
#         -Debug: activates the debug mode for very useful log messages
#   ConfigLoader: will load the configuration from a Config::General file in the
#                 application's home directory
# Static::Simple: will serve static files from the application's root
#                 directory

use Catalyst qw/
    ConfigLoader
    Static::Simple
    Authentication
    Authorization::Roles
    Session
    Session::Store::Redis
    NGCP::RealmCookie
    NGCP::EscapeSensitiveValue
    NGCP::EscapeJs
    NGCP::EscapeURI
    NGCP::License
    NGCP::Redis
    I18N
/;
use Log::Log4perl::Catalyst qw();
extends 'Catalyst';

our $VERSION = '0.01';

my $panel_config_file;
my $panel_config;
for my $path(qw#/etc/ngcp-panel/ngcp_panel.conf etc/ngcp_panel.conf ngcp_panel.conf#) {
    if (-f $path) {
        $panel_config_file = $path;
        last;
    }
}
$panel_config_file //= 'etc/ngcp_panel.conf';
$panel_config        = get_panel_config() // {};

sub get_panel_config {
    my $config;
    if( -f $panel_config_file ){
        my $catalyst_config = Config::General->new($panel_config_file);
        my %config = $catalyst_config->getall();
        $config = \%config;
    }
    return $config;
}

my $logger_config_file;
for my $path(qw#./logging.conf /etc/ngcp-panel/logging.conf#) {
    if(-f $path) {
        $logger_config_file = $path;
        last;
    }
}
$logger_config_file //= $panel_config_file;

sub handle_unicode_encoding_exception {
    my ($self, $exception_context) = @_;
    $self->log->debug(">> handle_unicode_encoding_exception: " . $exception_context->{error_msg});
    return $exception_context->{param_value};
}

__PACKAGE__->config(
    name => 'NGCP::Panel',
    # Disable deprecated behavior needed by old applications
    disable_component_resolution_regex_fallback => 1,
    enable_catalyst_header => 1, # Send X-Catalyst header
    encoding => 'UTF-8',
    'Plugin::ConfigLoader' => {
        file => $panel_config_file,
    },
    'View::HTML' => {
        INCLUDE_PATH => [
            '/usr/share/ngcp-panel/templates',
            '/usr/share/ngcp-panel/layout',
            '/usr/share/ngcp-panel/static',
            __PACKAGE__->path_to('share', 'templates'),
            __PACKAGE__->path_to('share', 'layout'),
            __PACKAGE__->path_to('share', 'static'),
        ],
        ABSOLUTE => 1,
        EVAL_PERL => 1,
    },
    'View::JSON' => {
        #Set the stash keys to be exposed to a JSON response
        #(sEcho iTotalRecords iTotalDisplayRecords aaData) for datatables
        expose_stash    => [ qw(sEcho iTotalRecords iTotalDisplayRecords iTotalRecordCountClipped iTotalDisplayRecordCountClipped aaData dt_custom_footer widget_data timeline_data) ],
    },
    'View::TT' => {
        INCLUDE_PATH => [
            '/usr/share/ngcp-panel/templates',
            '/usr/share/ngcp-panel/layout',
            '/usr/share/ngcp-panel/static',
            __PACKAGE__->path_to('share', 'templates'),
            __PACKAGE__->path_to('share', 'layout'),
            __PACKAGE__->path_to('share', 'static'),
        ],
        ABSOLUTE => 1,
        EVAL_PERL => 1,
    },

    'Plugin::Static::Simple' => {
        include_path => [
            '/usr/share/ngcp-panel/static',
            __PACKAGE__->path_to('share', 'static'),
        ],
        mime_types => {
            woff => 'application/x-font-woff',
        },
    },

    'Plugin::Session' => {
        flash_to_stash => 1,
        expires => 3600,
        cookie_secure => 1,
        cookie_name => 'ngcp-panel',
        login_to_csc_session_expiry => 86400, #24 hours expiry for subscriber sessions created directly from Admin UI
    },

    'Plugin::Authentication' => {
        default_realm => 'subscriber',
        admin => {
            credential => {
                class => 'Password',
                password_field => 'md5pass',
                password_type => 'hashed',
                password_hash_type => 'MD5'
            },
            store => {
                class => 'DBIx::Class',
                user_model => 'DB::admins',
                id_field => 'id',
                store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
                use_userdata_from_session => 0,
            }
        },
        admin_bcrypt => {
            credential => {
                class => 'Password',
                password_field => 'saltedpass',
                # we handle the salt and hash management manually in Login.pm
                password_type => 'clear',
            },
            store => {
                class => 'DBIx::Class',
                user_model => 'DB::admins',
                id_field => 'id',
                store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
                use_userdata_from_session => 0,
            }
        },
        admin_jwt => {
            credential => {
                class => '+NGCP::Panel::Authentication::Credential::JWT',
                username_jwt => 'username',
                username_field => 'login',
                id_jwt => 'id',
                id_field => 'id',
                jwt_key => _get_jwt_key(),
                debug => 1,
                alg => 'HS256',
            },
            store => {
                class => 'DBIx::Class',
                user_model => 'DB::admins',
                store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
            },
            use_session => 1,
        },
        api_admin => {
            credential => {
                class => 'Password',
                password_field => 'md5pass',
                password_type => 'hashed',
                password_hash_type => 'MD5'
            },
            store => {
                class => 'DBIx::Class',
                user_model => 'DB::admins',
                id_field => 'id',
                store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
                use_userdata_from_session => 0,
            },
            use_session => 0,
        },
        api_admin_bcrypt => {
            credential => {
                class => 'Password',
                password_field => 'saltedpass',
                # we handle the salt and hash management manually in Login.pm
                password_type => 'clear',
            },
            store => {
                class => 'DBIx::Class',
                user_model => 'DB::admins',
                id_field => 'id',
                store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
                use_userdata_from_session => 0,
            },
            use_session => 0,
        },
        api_admin_cert => {
            # TODO: should be NoPassword, but it's not available in our catalyst version yet
            credential => {
                class => 'Password',
                password_field => 'is_active',
                password_type => 'clear',
            },
            store => {
                class => 'DBIx::Class',
                user_model => 'DB::admins',
                id_field => 'id',
                store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
            },
            use_session => 0,
        },
        api_admin_http => {
            credential => {
                class => 'HTTP',
                #type => 'digest',
                type => 'basic',
                username_field => 'login',
                password_field => 'md5pass',
                password_type => 'hashed',
                password_hash_type => 'MD5'
            },
            store => {
                class => 'DBIx::Class',
                user_model => 'DB::admins',
                id_field => 'id',
                store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
            },
            use_session => 0,
        },
        api_subscriber_http => {
            credential => {
                class => 'HTTP',
                #type => 'digest',
                type => 'basic',
                username_field => 'webusername',
                password_field => 'webpassword',
                password_type => 'clear',
            },
            store => {
                class => 'DBIx::Class',
                user_model => 'DB::provisioning_voip_subscribers',
                store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
                use_userdata_from_session => 0,
            },
            use_session => 0,
        },
        api_subscriber_jwt => {
            credential => {
                class => '+NGCP::Panel::Authentication::Credential::JWT',
                username_jwt => 'username',
                username_field => 'webusername',
                id_jwt => 'subscriber_uuid',
                id_field => 'uuid',
                jwt_key => _get_jwt_key(),
                debug => 1,
                alg => 'HS256',
            },
            store => {
                class => 'DBIx::Class',
                user_model => 'DB::provisioning_voip_subscribers',
                store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
            },
            use_session => 0,
        },
        api_admin_jwt => {
            credential => {
                class => '+NGCP::Panel::Authentication::Credential::JWT',
                username_jwt => 'username',
                username_field => 'login',
                id_jwt => 'id',
                id_field => 'id',
                jwt_key => _get_jwt_key(),
                debug => 1,
                alg => 'HS256',
            },
            store => {
                class => 'DBIx::Class',
                user_model => 'DB::admins',
                store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
            },
            use_session => 0,
        },
        api_admin_system => {
            credential => {
                class => 'HTTP',
                type => 'basic',
                username_field => 'login',
                password_field => 'password',
                password_type => 'clear',
            },
            store => {
                class => '+NGCP::Panel::Authentication::Store::System',
                file  => '/etc/default/ngcp-api',
                group => 'auth_system',
            },
            use_session => 0,
        },
        subscriber => {
            credential => {
                class => 'Password',
                password_field => 'webpassword',
                password_type => 'clear',
            },
            store => {
                class => 'DBIx::Class',
                user_model => 'DB::provisioning_voip_subscribers',
                id_field => 'id',
                store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
                use_userdata_from_session => 0,
            }
        },
        ngcp_admin_ui_jwt => {
            credential => {
                class => '+NGCP::Panel::Authentication::Credential::JWT',
                username_jwt => 'username',
                username_field => 'login',
                id_jwt => 'id',
                id_field => 'id',
                jwt_key => _get_jwt_key(),
                debug => 1,
                alg => 'HS256',
            },
            store => {
                class => 'DBIx::Class',
                user_model => 'DB::admins',
                store_user_class => 'NGCP::Panel::Authentication::Store::RoleFromRealm',
            },
            use_session => 1,
        },
    },
    ngcp_version => get_ngcp_version(),
    uploadtmp    => $panel_config->{general}{tmpdir} // '/tmp',
);
__PACKAGE__->config( default_view => 'HTML' );

__PACKAGE__->log(Log::Log4perl::Catalyst->new($logger_config_file, autoflush => 1));
# autoflush => 1: since we are not writing directly to the log file but to syslog instead, let syslog/rsyslog take care of the buffering part, otherwise $c->log->* invokations get buffered and delayed until a request if fully completed and response is sent, on top of it all logs such dumped buffer to syslog will be logged with exact same time, which is not correct. As well as autoflush is enabled by default in Log4Perl.

# configure Data::HAL depending on our config
{
    require Data::HAL;
    *{Data::HAL::forcearray_policy} = sub {
        my ($self, $root, $property_type, $relation, $property) = @_;
        my $embedded = $root->embedded ? $root->embedded->[0] : undef;
        if ($embedded
            && ( $property_type eq 'links' || $property_type eq 'embedded' )
            && $relation =~/^ngcp:[a-z0-9]+$/
        ) {
            return 1;
        }
        if (!$embedded
            && ( $property_type eq 'links' )
            && $relation =~/^ngcp:[a-z0-9]+$/
        ) {
            return 1;
        }
    };
}

after setup_finalize => sub {

    my $app = shift;

    if ($ENV{NOTIFY_SOCKET}) {
        my $addr = $ENV{NOTIFY_SOCKET} =~ s/^@/\0/r;
        my $client = IO::Socket::UNIX->new(
            Type => SOCK_DGRAM(),
            Peer => $addr,
        ) or warn("can't connect to socket $ENV{NOTIFY_SOCKET}: $!\n");
        if ($client) {
            $client->autoflush(1);
            print $client "READY=1\n" or warn("can't send to socket $ENV{NOTIFY_SOCKET}: $!\n");
            close $client;
        }
    } else {
        warn("NOTIFY_SOCKET not set\n");
    }

};

# Start the application
__PACKAGE__->setup();

sub get_ngcp_version {
    my $content = File::Slurp::read_file("/etc/ngcp_version", err_mode => 'quiet');
    $content //= '(unavailable)';
    chomp($content);
    return $content;
}

sub _get_jwt_key {
    my $content = File::Slurp::read_file("/etc/ngcp-panel/jwt_secret", err_mode => 'quiet');
    $content //= '';
    $content =~ s/\n//; # remove newline before
    chomp($content);
    return $content;
}

1;

# vim: set tabstop=4 expandtab:
