package Koha::Notice::Util;

# Copyright Rijksmuseum 2023
#
# This file is part of Koha.
#
# Koha 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.
#
# Koha 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 Koha; if not, see <https://www.gnu.org/licenses>.

use Modern::Perl;
use Data::Dumper qw/Dumper/;

use C4::Context;
use Koha::DateUtils qw/dt_from_string/;
use Koha::Notice::Messages;

=head1 NAME

Koha::Notice::Util - Utility class related to Koha notice messages

=head1 CLASS METHODS

=head2 load_domain_limits

    my $domain_limits = Koha::Notice::Util->load_domain_limits;

=cut

sub load_domain_limits {
    my ($class) = @_;

    my $domain_limits;
    my $entry = C4::Context->config('message_domain_limits');
    if ( ref($entry) eq 'HASH' ) {
        if ( exists $entry->{domain} ) {

            # Turn single hash entry into array
            $domain_limits =
                ref( $entry->{domain} ) eq 'HASH'
                ? [ $entry->{domain} ]
                : $entry->{domain};

            # Convert to hash structure by domain name
            $domain_limits = { map { _init_domain_entry($_); } @$domain_limits };
        }
    }
    return $domain_limits;
}

sub _init_domain_entry {
    my ($config_entry) = @_;

    # Return either a hash like ( name => { limit => , unit =>, count => } ) for regular entries
    # or return a hash like ( name => { belongs_to => } ) for a domain that is part of a group

    return if ref($config_entry) ne 'HASH' || !exists $config_entry->{name};
    my $elements;
    if ( $config_entry->{belongs_to} ) {
        $elements = { belongs_to => lc $config_entry->{belongs_to} };
    } else {
        $elements = { limit => $config_entry->{limit}, unit => $config_entry->{unit}, count => undef };
    }
    return ( lc $config_entry->{name}, $elements );
}

=head2 exceeds_limit

    my $boolean = Koha::Notice::Util->exceeds_limit({ to => $to_address, limits => $domain_limits, incr => 1|0 });

=cut

sub exceeds_limit {
    my ( $class, $params ) = @_;
    my $domain_limits = $params->{limits} or return 0;    # no limits at all
    my $to_address    = $params->{to}     or return 0;    # no address, no limit exceeded
    my $incr          = $params->{incr} // 1;             # by default we increment

    my $domain = q{};
    $domain = lc $1 if $to_address && $to_address =~ /@(\H+)/;
    return 0 if !$domain || !exists $domain_limits->{$domain};

    # Keep in mind that domain may be part of group count
    my $group = $domain_limits->{$domain}->{belongs_to} // $domain;
    _get_domain_count( $domain, $group, $domain_limits ) if !defined $domain_limits->{$group}->{count};
    return 1 if $domain_limits->{$group}->{count} >= $domain_limits->{$group}->{limit};

    if ($incr) {
        $domain_limits->{$group}->{count}++;
        warn "Sending messages: domain $group reached limit of "
            . $domain_limits->{$group}->{limit} . '/'
            . $domain_limits->{$group}->{unit}
            if $domain_limits->{$group}->{count} == $domain_limits->{$group}->{limit};
    }
    return 0;
}

=head1 PRIVATE METHODS

=cut

sub _get_domain_count {
    my ( $domain, $group, $limits ) = @_;

    # Check if there are group members too
    my @domains;
    push @domains, $domain if $domain eq $group;
    push @domains, map {
        my $belongs = $limits->{$_}->{belongs_to} // q{};
        $belongs eq $group ? $_ : ();
    } keys %$limits;

    my $sum      = 0;
    my $start_dt = _convert_unit( undef, $limits->{$group}->{unit} );
    foreach my $domain (@domains) {
        $sum += Koha::Notice::Messages->search(
            {
                message_transport_type => 'email',
                status                 => 'sent',
                to_address             => { 'LIKE', '%' . $domain },
            }
        )->filter_by_last_update( { timestamp_column_name => 'updated_on', from => $start_dt } )->count;
    }
    $limits->{$group}->{count} = $sum;
}

sub _convert_unit {    # unit should be like \d+(m|h|d)
    my ( $dt, $unit ) = @_;
    $dt //= dt_from_string();
    if ( $unit && $unit =~ /(\d+)([mhd])/ ) {
        my $abbrev = { m => 'minutes', h => 'hours', d => 'days' };
        foreach my $h ( 0, 1 ) {    # try hour before too when subtract fails (like: change to summertime)
            eval { $dt->subtract( hours => $h )->subtract( $abbrev->{$2} => $1 ) } and last;
        }
    }
    return $dt;
}

1;
