#!/usr/bin/perl

use strict;
use warnings;
use utf8;
use lib qw(@modulesdir@);
use feature "switch";
no warnings 'experimental::smartmatch';

use Data::Dumper;
use English qw(-no_match_vars);
use Getopt::Long qw(:config auto_help);
use Log::Any::Adapter;
use Pod::Usage;

use IdPAccountManager::Data::AuthenticationToken;
use IdPAccountManager::Data::AuthenticationToken::Manager;
use IdPAccountManager::Data::ServiceProvider;
use IdPAccountManager::Data::ServiceProvider::Manager;
use IdPAccountManager::Data::TestAccount;
use IdPAccountManager::Data::TestAccount::Manager;
use IdPAccountManager::Configuration;
use IdPAccountManager::SAMLMetadata;

my %options;
GetOptions(
    \%options,
    'account_profile=s',
    'contacts=s',
    'delete',
    'displayname=s',
    'email_address=s',
    'filter_expired',
    'sp_entityid=s',
    'token=s',
) or pod2usage(
    -message => "unknown option, aborting\n",
    -verbose => 0
);

my $action = $ARGV[0];

pod2usage(
    -message => "no action given, aborting\n",
    -verbose => 0
) unless $action;

my $configuration = IdPAccountManager::Configuration->new(
    file => '@sysconfdir@/manager.conf'
);

IdPAccountManager::DB->register_db(
    driver   => $configuration->{database_type},
    database => $configuration->{database_name},
    host     => $configuration->{database_host},
    password => $configuration->{database_password},
    username => $configuration->{database_user}
);

my $db = IdPAccountManager::DB->new();

for ($action) {
    when ('add_test_account')           { add_test_account()           }
    when ('list_test_accounts')         { list_test_accounts()         }
    when ('add_service_provider')       { add_service_provider()       }
    when ('list_service_providers')     { list_service_providers()     }
    when ('add_authentication_token')   { add_authentication_token()   }
    when ('get_authentication_token')   { get_authentication_token()   }
    when ('list_authentication_tokens') { list_authentication_tokens() }
    when ('parse_federation_metadata')  { parse_federation_metadata()  }
    default {
        pod2usage(
            -message => "invalid action '$action', aborting\n",
            -verbose => 0
        );
    }
}

sub add_test_account {
    pod2usage(
        -message => "missing account_profile option, aborting\n",
        -verbose => 0
    ) unless $options{account_profile};

    pod2usage(
        -message => "missing sp_entityid option, aborting\n",
        -verbose => 0
    ) unless $options{sp_entityid};

    my $test_account = IdPAccountManager::Data::TestAccount->new(
        db              => $db,
        account_profile => $options{account_profile},
        sp_entityid     => $options{sp_entityid}
    );

    die "Failed to create test account\n"
        unless $test_account;

    die "Failed to save test account\n"
        unless $test_account->save(
            accounts_validity_period => $configuration->{accounts_validity_period}
        );

    printf "Account created:\n\tuserid: user%d\n\tpassword: %s\n",
      $test_account->id(), $test_account->user_password();

}

sub list_test_accounts {
    my %args;
    if ($options{sp_entityid}) {
        push @{ $args{query} }, sp_entityid => $options{sp_entityid};
    }

    if ($options{account_profile}) {
        push @{ $args{query} }, account_profile => $options{account_profile};
    }

    if ($options{filter_expired}) {
        push @{ $args{query} }, expiration_date => { lt => time };
    }

    my $accounts =
        IdPAccountManager::Data::TestAccount::Manager->get_testaccounts(db => $db, %args);

    if (! @$accounts) {
        printf "No matching test account in DB\n";
    }

    foreach my $account (@$accounts) {
        $account->print();
        next unless $options{delete};
        die "failed to delete test account\n"
            unless $account->delete();
    }

    if ($options{delete}) {
        printf "%d accounts removed\n", @$accounts;

        die "failed to update simpleSAMLphp configuration file\n"
            unless IdPAccountManager::Tools::update_ssp_authsources(
                $configuration->{templates_dir},
                $configuration
            );

        printf "Update simpleSamlPhp configuration file...\n";
    }

}

sub parse_federation_metadata {
    my $federation_metadata;

    eval {
        $federation_metadata = IdPAccountManager::SAMLMetadata->new(
            file => $configuration->{federation_metadata_file_path}
        );
    };
    die "unable to load federation metadata: $EVAL_ERROR" if $EVAL_ERROR;

    my $data = $federation_metadata->parse(
        entity_id => $options{sp_entityid},
    );

    printf "Document %s parsed\n",
      $configuration->{federation_metadata_file_path};

    ## List SAML entities
    printf "Hashref representing the metadata:\n";
    print Data::Dumper->Dump($data);
}

sub add_service_provider {

    pod2usage(
        -message => "missing sp_entityid option, aborting\n",
        -verbose => 0
    ) unless $options{sp_entityid};

    pod2usage(
        -message => "missing contacts option, aborting\n",
        -verbose => 0
    ) unless $options{contacts};

    ## Check if entry already exists in DB first
    my $service_provider =
      IdPAccountManager::Data::ServiceProvider->new(
        db       => $db,
        entityid => $options{sp_entityid}
    );
    if ($service_provider->load(speculative => 1)) {
        printf "Entry for %s already in DB; update it with new data\n",
          $options{sp_entityid};

        $service_provider->contacts($options{contacts});
        $service_provider->displayname($options{displayname})
          if ($options{displayname});
    } else {

        $service_provider = IdPAccountManager::Data::ServiceProvider->new(
            db          => $db,
            entityid    => $options{sp_entityid},
            contacts    => $options{contacts},
            displayname => $options{displayname}
        );
        die "failed to create service provider\n"
            unless $service_provider;
    }

    die "failed to save service provider"
        unless $service_provider->save();

    printf "Service Provider created\n";

}

sub list_service_providers {
    my %args;

    my $providers = IdPAccountManager::Data::ServiceProvider::Manager->get_serviceproviders(db => $db, %args);

    if (@$providers) {
        printf "No service provider in DB\n";
    }

    foreach my $provider (@$providers) {
        $provider->print();
    }

}

sub list_authentication_tokens {

    my %args;
    if ($options{sp_entityid}) {
        push @{ $args{query} }, sp_entityid => $options{sp_entityid};
    }
    if ($options{token}) {
        push @{ $args{query} }, token => $options{token};
    }
    if ($options{filter_expired}) {
        push @{ $args{query} }, creation_date =>
          { lt => time - ($configuration->{tokens_validity_period} * 3600) };
    }

    my $tokens =
      IdPAccountManager::Data::AuthenticationToken::Manager->get_authenticationtokens(db => $db, %args);

    if (!@$tokens) {
        printf "No corresponding token found in DB\n";
    }

    foreach my $token (@$tokens) {
        $token->print();
        next unless $options{delete};
        die "failed to delete authentication token\n"
            unless $token->delete();
    }

    if ($options{delete}) {
        printf "%d tokens removed\n", @$tokens;

    }

}

sub get_authentication_token {

    my %args;
    if ($options{token}) {
        $args{token} = $options{token};
    }

    my $authentication_token =
      IdPAccountManager::AuthenticationToken->new(db => $db, %args);

    die "No corresponding token found in DB\n"
        unless $authentication_token->load();

    if ($options{sp_entityid}) {
        die "Authentication token cannot be used for this SP\n"
            unless $authentication_token->get('sp_entityid')
                eq $options{sp_entityid};
    }

    $authentication_token->print();

}

sub add_authentication_token {

    pod2usage(
        -message => "missing email_address option, aborting\n",
        -verbose => 0
    ) unless $options{email_address};

    pod2usage(
        -message => "missing sp_entityid option, aborting\n",
        -verbose => 0
    ) unless $options{sp_entityid};

    my $authentication_token = IdPAccountManager::Data::AuthenticationToken->new(
        db            => $db,
        email_address => $options{email_address},
        sp_entityid   => $options{sp_entityid}
    );

    die "failed to create authentication token\n"
        unless $authentication_token;

    ## First remove token if on exist for this email+SP
    if ($authentication_token->load(speculative => 1)) {
        die "failed to delete authentication token\n"
            unless $authentication_token->delete();

        $authentication_token = IdPAccountManager::Data::AuthenticationToken->new(
            db            => $db,
            email_address => $options{email_address},
            sp_entityid   => $options{sp_entityid}
        );
        die "failed to create authentication token\n"
            unless $authentication_token;
    }

    die "failed to save authentication token\n"
        unless $authentication_token->save();

    $authentication_token->print();

}

__END__

=head1 NAME

account-manager-client.pl - Command line client to the Test IdP Account manager

=head1 SYNOPSIS

B<account-manager-client.pl> B<add_test_account>
S<B<--account_profile> I<string>>
S<B<--sp_entityid> I<string>>

B<account-manager-client.pl> B<list_test_accounts>
S<[B<--account_profile> I<string>]>
S<[B<--sp_entityid> I<string>]>
[B<--filter_expired>]
[B<--delete>]

B<account-manager-client.pl> B<parse_federation_metadata>
S<[B<--sp_entityid> I<string>]>

B<account-manager-client.pl> B<add_service_provider>
S<B<--sp_entityid> I<string>>
S<B<--contact> I<string>>
S<[B<--displayname> I<string>]>

B<account-manager-client.pl> B<list_service_providers>

B<account-manager-client.pl> B<list_authentication_tokens>
S<B<--sp_entityid> I<string>>
S<[B<--token> I<string>]>
[B<--filter_expired>]
[B<--delete>]

B<account-manager-client.pl> B<get_authentication_token>
S<B<--sp_entityid> I<string>>
S<[B<--token> I<string>]>

B<account-manager-client.pl> B<add_authentication_token>
S<B<--sp_entityid> I<string>>
S<B<--email_address> I<string>>

=head1 DESCRIPTION

The Test Account manager instanciates test accounts associated to a SAML
Identity Provider.  This script provides a command-line interface for most
functions.

=head1 EXAMPLES

    $> account-manager-client.pl add_test_account \
    --sp_entityid https://test.federation.renater.fr/test/ressource \
    --account_profile student1

Adds a new test account.

    $> account-manager-client.pl list_test_accounts \
    --sp_entityid https://test.federation.renater.fr/test/ressource \
    --account_profile student1

List all test accounts. Criterias can be added to filter test accounts.

    $> account-manager-client.pl list_test_accounts --filter_expired

List all expired test accounts.

    $> account-manager-client.pl list_test_accounts --filter_expired \
    --delete

Remove all expired test accounts from DB.

    $> account-manager-client.pl parse_federation_metadata

Parses the SAML metadata file, as defined by the
C<federation_metadata_file_path> configuration parameter.

    $> account-manager-client.pl list_authentication_tokens \
    --sp_entityid https://test.federation.renater.fr/test/ressource \
    --token dhj67sjJ

List all authentication tokens. Criterias can be added to filter tokens.

    $> account-manager-client.pl list_authentication_tokens \
    --filter_expired

List all expired authentication tokens.

    $> account-manager-client.pl list_authentication_tokens \
    --filter_expired --delete

Remove all expired authentication tokens from DB.

    $> account-manager-client.pl get_authentication_token \
    --token dhj67sjJ

Get informations on a token.

    $> account-manager-client.pl add_authentication_token \
    --email_address john@my.fqdn \
    --sp_entityid https://test.federation.renater.fr/test/ressource

Adds a new test account.

    $> account-manager-client.pl add_service_provider \
    --sp_entityid https://test.federation.renater.fr/test/ressource \
    --displayname 'Test SP' --contacts email1@dom,email2@dom

Adds a new Service provider
