package Dancer2::Plugin::JobScheduler;
use strict;
use warnings;

# ABSTRACT: Plugin for Dancer2 web app to send and query jobs in different job schedulers

our $VERSION = '0.001'; # VERSION: generated by DZP::OurPkgVersion

use Carp;
use English '-no_match_vars';
use Module::Load;

use Log::Any qw( $log ), hooks => { build_context => [ \&_build_context, ], };
use Log::Any::Adapter::Util;

sub _build_context {

    # my ($level, $category, $data) = @_;
    my %ctx;
    my @caller = Log::Any::Adapter::Util::get_correct_caller();
    $ctx{file} = $caller[1];
    $ctx{line} = $caller[2];
    return %ctx;
}

use Dancer2::Plugin;

plugin_keywords qw(
  submit_job
  list_jobs
);

has default => (
    is  => 'ro',
    isa => sub {
        croak if ( !( $_[0] =~ m/^[[:word:]]{1,}$/msx ) );
    },
    from_config => 'default',
);

has schedulers => (
    is  => 'ro',
    isa => sub {
        croak if ( ref $_[0] ne 'HASH' );
    },
    from_config => 'schedulers',
);

sub _verify_configuration {
    my ($self) = @_;
    if ( $self->default ) {
        if ( exists $self->schedulers->{ $self->default } ) {
            return $self->default;
        }
        else {
            my $e = 'Invalid value in config: plugins->JobScheduler->default: ' . '\'%s\', no matching job scheduler';
            $log->errorf( $e, $self->default );
            croak sprintf $e, $self->default;
        }
    }
    else {
        if ( scalar keys %{ $self->schedulers } > 1 ) {
            my $e = 'Default job queue missing in config. Must define ' . 'default job queue when there is more than one job queue';
            $log->errorf($e);
            croak $e;
        }
        elsif ( scalar keys %{ $self->schedulers } <= 0 ) {
            my $e = 'Invalid config. Must have at least one job queue';
            $log->errorf($e);
            croak $e;
        }
        else {
            return ( keys %{ $self->schedulers } )[0];
        }
    }
    return;
}

# A client (of a client) of the job queue
# or other object via which the jobs are submitted
has _clients => (
    is  => 'lazy',
    isa => sub {
        croak if ( ref $_[0] ne 'HASH' );
    },
);

sub _build__clients {    ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
    my $self = shift;
    $self->_verify_configuration();
    my %h;
    foreach my $key ( keys %{ $self->schedulers } ) {
        my $s = $self->schedulers->{$key};
        my $client_name =
            $s->{'client'} =~ m/::/msx
          ? $s->{'client'}
          : "Dancer2::Plugin::JobScheduler::Client::$s->{'client'}";

        # Does it exist?
        {
            local $EVAL_ERROR = $EVAL_ERROR;
            my $r = eval { load "$client_name"; 1; };
            if ( !$r || $EVAL_ERROR ) {
                my $e = 'Failed to load job queue client \'%s\', error: %s';
                $log->errorf( $e, $client_name, $EVAL_ERROR );
                croak sprintf $e, $client_name, $EVAL_ERROR;
            }
        }

        # Can we instantiate it?
        my $scheduler;
        {
            local $EVAL_ERROR = $EVAL_ERROR;
            my $r = eval {
                $scheduler = $client_name->new( config => $s->{'parameters'}, );
                1;
            };
            if ( !$r || $EVAL_ERROR ) {
                my $e = 'Failed to instantiate job queue client \'%s\', error: %s';
                $log->errorf( $e, $client_name, $EVAL_ERROR );
                croak sprintf $e, $client_name, $EVAL_ERROR;
            }
        }
        $h{$key} = $scheduler;
    }
    return \%h;
}

sub submit_job {
    my ( $self, %args ) = @_;
    my $client_key = $args{client} ? $args{client} : $self->default;
    my $job        = $args{job};
    $log->debugf( '_clients: %s', $self->_clients );
    my $client = $self->_clients->{$client_key};
    return ( $client->submit_job($job) );
}

sub list_jobs {
    my ( $self, %args ) = @_;
    my $client_key = $args{client} ? $args{client} : $self->default;
    my $client     = $self->_clients->{$client_key};
    my @jobs       = $client->list_jobs( $args{'search_params'} );
    return @jobs;
}

1;

=pod

=encoding UTF-8

=head1 NAME

Dancer2::Plugin::JobScheduler - Plugin for Dancer2 web app to send and query jobs in different job schedulers

=head1 VERSION

version 0.001

=head1 SYNOPSIS

    use Dancer2;
    BEGIN {
        my %plugin_config = (
            default => 'theschwartz',
            schedulers => {
                theschwartz => {
                    client => 'TheSchwartz',
                    parameters => {
                        handle_uniqkey => 'acknowledge',
                        databases => {
                            theschwartz_db1 => {
                                prefix => q{},
                                dbh_callback => 'Database::ManagedHandle->instance',
                            },
                        }
                    }
                }
            }
        );
        set log => 'debug';
        set plugins => {
            JobScheduler => \%plugin_config,
        };
    }
    use Dancer2::Plugin::JobScheduler;

    set serializer => 'JSON';

    get q{/submit_job} => sub {
        my %r = submit_job(
            client => 'theschwartz',
            job => {
                task => 'task1',
                args => { name => 'My Name', age => 123 },
                opts => {},
            },
        );
        return to_json(\%r);
    };

    get q{/list_jobs} => sub {
        my %r = list_jobs(
            client => 'theschwartz',
            search_params => {
                task => 'task1',
            },
        );
        return to_json(\%r);
    };

=head1 DESCRIPTION

This package is an interface to access different
L<job schedulers|https://en.wikipedia.org/wiki/Job_scheduler> in L<Dancer2>
web app. Supported job schedulers:

=over 8

=item L<TheSchwartz>

=back

Dancer2::Plugin::JobScheduler provides an interface to submit jobs
and query jobs currently in queue. As a Dancer2 plugin, it creates two
new commands in the web app: C<submit> and C<list_jobs>.

=begin comments




=end comments

Lots of subs not covered by Pod::Coverage because they are inherited from
Dancer2::Plugin.

=for Pod::Coverage ClassHooks PluginKeyword dancer_app execute_plugin_hook hook
=for Pod::Coverage list_jobs on_plugin_import plugin_args plugin_setting
=for Pod::Coverage register register_hook register_plugin request submit_job var

=head1 AUTHOR

Mikko Koivunalho <mikkoi@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2023 by Mikko Koivunalho.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut

__END__
config:
    JobScheduler:
        # Establish connection / open database to test it works
        # when creating the object
        # not-lazy-init: true ??
        # test-before-run: false
        # default: theschwartz
        default: theschwartz
        schedulers:
            theschwartz:
                client: TheSchwartz
                parameters:
                    databases:
                        # This TheSchwartz job scheduler has three databases
                        -
                            type: create_database_handle
                            dsn: dbi:Pg:dbname=${ENV:THESCHWARTZ_DATABASE_NAME};host=${ENV:THESCHWARTZ_DATABASE_HOST};port=${ENV:THESCHWARTZ_DATABASE_PORT}
                            username: ${ENV:THESCHWARTZ_DATABASE_USERNAME}
                            password: ${ENV:THESCHWARTZ_DATABASE_PASSWORD}
                            # dbi_params:
                            # connection_args:
                            #     ReadOnly: 0
                            #     AutoCommit: 0
                            #     PrintError: 0
                            #     RaiseError: 1
                            #     printWarn: 0
                            #     RaiseWarn: 1
                            #     TaintIn: 1
                            #     TaintOut: 1
                            #     pg_server_prepare: 0
                            #     TraceLevel: 0
                            prefix: ${ENV:THESCHWARTZ_DATABASE_SCHEMA}.
                        -
                        # Second TheSchwartz database
                            dsn: dbi:Pg:dbname=${ENV:THESCHWARTZ_DATABASE_NAME};host=${ENV:THESCHWARTZ_DATABASE_HOST};port=${ENV:THESCHWARTZ_DATABASE_PORT}
                            username: ${ENV:THESCHWARTZ_DATABASE_USERNAME}
                            password: ${ENV:THESCHWARTZ_DATABASE_PASSWORD}
                            prefix: ${ENV:THESCHWARTZ_DATABASE_SCHEMA}.
                        -
                        # Third TheSchwartz database
                            type: dancer_plugin_database_connection
                            # connection_key is not mandatory, otherwise use default.
                            connection_key: rw
            # Second TheSchwartz job scheduler
            theschwartz2:
                client: TheSchwartz
                parameters:
                  database:
                      driver: Pg
                      prefix: ${ENV:THESCHWARTZ_DATABASE_SCHEMA}.
