package Lemonldap::NG::Portal::Lib::Choice;

use strict;
use Mouse;
use Safe;

extends 'Lemonldap::NG::Portal::Lib::Wrapper';
with 'Lemonldap::NG::Portal::Lib::OverConf';

our $VERSION = '2.0.0';

has modules => ( is => 'rw', default => sub { {} } );

has rules => ( is => 'rw', default => sub { {} } );

has type => ( is => 'rw' );

has catch => ( is => 'rw', default => sub { {} } );

has sessionKey => ( is => 'ro', default => '_choice' );

my $_choiceRules;

# INITIALIZATION

# init() must be called by module::init() with a number:
#  - 0 for auth
#  - 1 for userDB
#  - 2 for passwordDB ?
sub init {
    my ( $self, $type ) = @_;
    $self->type($type);

    unless ( $self->conf->{authChoiceModules}
        and %{ $self->conf->{authChoiceModules} } )
    {
        $self->error("'authChoiceModules' is empty");
        return 0;
    }

    foreach my $name ( keys %{ $self->conf->{authChoiceModules} } ) {
        my @mods =
          split( /[;\|]/, $self->conf->{authChoiceModules}->{$name} );
        my $module = '::'
          . [ 'Auth', 'UserDB', 'Password' ]->[$type] . '::'
          . $mods[$type];
        my $over;
        if ( $mods[5] ) {
            eval { $over = JSON::from_json( $mods[5] ) };
            if ($@) {
                $self->logger->error("Bad over value ($@), skipped");
            }
        }
        if ( $module = $self->loadModule( $module, $over ) ) {
            $self->modules->{$name} = $module;
            $self->logger->debug(
                [qw(Authentication User Password)]->[$type]
                  . " module $name selected" );
        }
        else {
            $self->logger->error(
                "Choice: unable to load $name, disabling it: " . $self->error );
            $self->error('');
        }

        # Test if auth module wants to catch some path
        unless ($type) {
            if ( $module->can('catch') ) {
                $self->catch->{$name} = $module->catch;
            }
        }

        # Display conditions
        my $safe = Safe->new;
        my $cond = $mods[4];
        if ( defined $cond and $cond !~ /^$/ ) {
            $self->logger->debug("Found rule $cond for $name");
            $_choiceRules->{$name} =
              $safe->reval("sub{my(\$env)=\@_;return ($cond)}");
            if ($@) {
                $self->logger->error("Bad condition $cond: $@");
                return 0;
            }
        }
        else {
            $self->logger->debug("No rule for $name");
            $_choiceRules->{$name} = sub { 1 };
        }
    }
    unless ( keys %{ $self->modules } ) {
        $self->error('Choice: no available modules found, aborting');
        return 0;
    }
    return 1;
}

# RUNNING METHODS

sub checkChoice {
    my ( $self, $req ) = @_;
    my $name;

    # Check Choice from pdata
    if ( defined $req->pdata->{_choice} ) {

        $name = $req->pdata->{_choice};
        $self->logger->debug("Choice $name selected from pdata");
    }

    unless ($name) {

        # Check with catch method
        foreach ( keys %{ $self->catch } ) {
            if ( $req->path_info =~ $self->catch->{$_} ) {
                $name = $_;
                $self->logger->debug(
                    "Choice $name selected from " . $req->path_info );
                last;
            }
        }
    }

    unless ($name) {

        # Check with other methods
        $name ||=
             $req->param( $self->conf->{authChoiceParam} )
          || $req->userData->{_choice}
          || $req->sessionInfo->{_choice}
          or return 0;

        $self->logger->debug("Choice $name selected");
    }

    unless ( defined $self->modules->{$name} ) {
        $self->logger->error("Unknown choice '$name'");
        return 0;
    }

    # Store choice if module loops
    $req->pdata->{_choice}    = $name;
    $req->data->{_authChoice} = $name;
    return $name if ( $req->data->{ "enabledMods" . $self->type } );
    $req->sessionInfo->{_choice} = $name;
    $req->data->{ "enabledMods" . $self->type } = [ $self->modules->{$name} ];
    $self->p->_authentication->authnLevel("${name}AuthnLevel");
    return $name;
}

sub name {
    my ( $self, $req, $type ) = @_;
    unless ($req) {
        return 'Choice';
    }
    my $n = ref( $req->data->{ "enabledMods" . $self->type }->[0] );
    $n =~ s/^Lemonldap::NG::Portal::(?:(?:UserDB|Auth)::)?//;
    return $n;
}

package Lemonldap::NG::Portal::Main;

# Build authentication loop displayed in template
# Return authLoop array reference
sub _buildAuthLoop {
    my ( $self, $req ) = @_;
    my @authLoop;

    # Test authentication choices
    unless ( ref $self->conf->{authChoiceModules} eq 'HASH' ) {
        $self->logger->warn("No authentication choices defined");
        return [];
    }

    foreach ( sort keys %{ $self->conf->{authChoiceModules} } ) {

        my $name = $_;

        # Name can have a digit as first character
        # for sorting purpose
        # Remove it in displayed name
        $name =~ s/^(\d*)?(\s*)?//;

        # Replace also _ by space for a nice display
        $name =~ s/\_/ /g;

        # Find modules associated to authChoice
        my ( $auth, $userDB, $passwordDB, $url, $condition ) =
          split( /[;\|]/, $self->conf->{authChoiceModules}->{$_} );

        unless ( $_choiceRules->{$_} ) {
            $self->logger->error("$_ has no rule !!!");
            $_choiceRules->{$_} = sub { 1 };
        }
        unless ( $_choiceRules->{$_}->( $req->env ) ) {
            $self->logger->debug(
"Condition returns false, authentication choice $_ will not be displayed"
            );
        }
        else {
            $self->logger->debug("Displaying authentication choice $_");
            if ( $auth and $userDB and $passwordDB ) {

                # Default URL
                $req->{cspFormAction} ||= '';
                if (
                    defined $url
                    and not $self->checkXSSAttack( 'URI',
                        $req->env->{'REQUEST_URI'} )
                    and $url =~
                    q%^(https?://)?[^\s/.?#$].[^\s]+$% # URL must be well formatted
                  )
                {
                    #$url .= $req->env->{'REQUEST_URI'};

                    # Avoid append same URL
                    $req->{cspFormAction} .= " $url"
                      unless $req->{cspFormAction} =~ qr%\b$url\b%;
                }
                else {
                    $url .= '#';
                }
                $self->logger->debug("Use URL $url");

                # Options to store in the loop
                my $optionsLoop = {
                    name   => $name,
                    key    => $_,
                    module => $auth,
                    url    => $url
                };

                # Get displayType for this module
                no strict 'refs';
                my $displayType = "Lemonldap::NG::Portal::Auth::${auth}"
                  ->can('getDisplayType')->( $self, $req );

                $self->logger->debug(
                    "Display type $displayType for module $auth");
                $optionsLoop->{$displayType} = 1;
                my $logo = $_;
                if ( $auth eq 'Custom' ) {
                    $logo =
                      ( $self->{conf}->{customAuth} =~ /::(\w+)$/ )[0];
                }

                # If displayType is logo, check if key.png is available
                if (  -e $self->conf->{templateDir}
                    . "/../htdocs/static/common/modules/"
                    . $logo
                    . ".png" )
                {
                    $optionsLoop->{logoFile} = $logo . ".png";
                }
                else {
                    $optionsLoop->{logoFile} = $auth . ".png";
                }

                # Register item in loop
                push @authLoop, $optionsLoop;

                $self->logger->debug(
                    "Authentication choice $name will be displayed");
            }
            else {
                $req->error("Authentication choice $_ value is invalid");
                return 0;
            }
        }

    }

    return \@authLoop;

}

1;

