package Koha::Patron::MessagePreference;

# Copyright Koha-Suomi Oy 2016
#
# 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 Koha::Database;
use Koha::Exceptions;
use Koha::Exceptions::Patron::MessagePreference;
use Koha::Exceptions::Patron::MessagePreference::Transport;
use Koha::Exceptions::Patron::Category;
use Koha::Patron::Categories;
use Koha::Patron::MessagePreference::Attributes;
use Koha::Patron::MessagePreferences;
use Koha::Patron::MessagePreference::Transport::Preferences;
use Koha::Patron::MessagePreference::Transport::Types;
use Koha::Patron::MessagePreference::Transports;
use Koha::Patrons;

use base qw(Koha::Object);

=head1 NAME

Koha::Patron::MessagePreference - Koha Patron Message Preference object class

=head1 API

=head2 Class Methods

=cut

=head3 new

my $preference = Koha::Patron::MessagePreference->new({
   borrowernumber => 123,
   #categorycode => 'ABC',
   message_attribute_id => 4,
   message_transport_types => ['email', 'sms'], # see documentation below
   wants_digest => 1,
   days_in_advance => 7,
});

Takes either borrowernumber or categorycode, but not both.

days_in_advance may not be available. See message_attributes table for takes_days
configuration.

wants_digest may not be available. See message_transports table for is_digest
configuration.

You can instantiate a new object without custom validation errors, but when
storing, validation may throw exceptions. See C<validate()> for more
documentation.

C<message_transport_types> is a parameter that is not actually a column in this
Koha-object. Given this parameter, the message transport types will be added as
related transport types for this object. For get and set, you can access them via
subroutine C<message_transport_types()> in this class.

=cut

sub new {
    my ( $class, $params ) = @_;

    my $types = $params->{'message_transport_types'};
    delete $params->{'message_transport_types'};

    my $self = $class->SUPER::new($params);

    $self->_set_message_transport_types($types);

    return $self;
}

=head3 new_from_default

my $preference = Koha::Patron::MessagePreference->new_from_default({
    borrowernumber => 123,
    categorycode   => 'ABC',   # if not given, patron's categorycode will be used
    message_attribute_id => 1,
});

NOTE: This subroutine initializes and STORES the object (in order to set
message transport types for the preference), so no need to call ->store when
preferences are initialized via this method.

Stores default messaging preference for C<categorycode> to patron for given
C<message_attribute_id>.

Throws Koha::Exceptions::MissingParameter if any of following is missing:
- borrowernumber
- message_attribute_id

Throws Koha::Exceptions::ObjectNotFound if default preferences are not found.

=cut

sub new_from_default {
    my ( $class, $params ) = @_;

    my @required = qw(borrowernumber message_attribute_id);
    foreach my $p (@required) {
        Koha::Exceptions::MissingParameter->throw(
            error => "Missing required parameter '$p'.",
        ) unless exists $params->{$p};
    }
    unless ( $params->{'categorycode'} ) {
        my $patron = Koha::Patrons->find( $params->{borrowernumber} );
        $params->{'categorycode'} = $patron->categorycode;
    }

    my $default = Koha::Patron::MessagePreferences->find(
        {
            categorycode         => $params->{'categorycode'},
            message_attribute_id => $params->{'message_attribute_id'},
        }
    );
    Koha::Exceptions::ObjectNotFound->throw(
        error => 'Default messaging preference for given categorycode and' . ' message_attribute_id cannot be found.',
    ) unless $default;
    $default = $default->unblessed;

    # Add a new messaging preference for patron
    my $self = $class->SUPER::new(
        {
            borrowernumber       => $params->{'borrowernumber'},
            message_attribute_id => $default->{'message_attribute_id'},
            days_in_advance      => $default->{'days_in_advance'},
            wants_digest         => $default->{'wants_digest'},
        }
    )->store;

    # Set default messaging transport types
    my $default_transport_types =
        Koha::Patron::MessagePreference::Transport::Preferences->search(
        { borrower_message_preference_id => $default->{'borrower_message_preference_id'} } );
    while ( my $transport = $default_transport_types->next ) {
        Koha::Patron::MessagePreference::Transport::Preference->new(
            {
                borrower_message_preference_id => $self->borrower_message_preference_id,
                message_transport_type         => $transport->message_transport_type,
            }
        )->store;
    }

    return $self;
}

=head3 message_name

$preference->message_name

Gets message_name for this messaging preference.

Setter not implemented.

=cut

sub message_name {
    my ($self) = @_;

    if ( $self->{'_message_name'} ) {
        return $self->{'_message_name'};
    }
    $self->{'_message_name'} = Koha::Patron::MessagePreference::Attributes->find(
        {
            message_attribute_id => $self->message_attribute_id,
        }
    )->message_name;
    return $self->{'_message_name'};
}

=head3 message_transport_types

$preference->message_transport_types
Returns a HASHREF of message transport types for this messaging preference, e.g.
if ($preference->message_transport_types->{'email'}) {
    # email is one of the transport preferences
}

$preference->message_transport_types('email', 'sms');
Sets the given message transport types for this messaging preference

=cut

sub message_transport_types {
    my $self = shift;

    unless (@_) {
        if ( $self->{'_message_transport_types'} ) {
            return $self->{'_message_transport_types'};
        }
        map {
            my $transport = Koha::Patron::MessagePreference::Transports->find(
                {
                    message_attribute_id   => $self->message_attribute_id,
                    message_transport_type => $_->message_transport_type,
                    is_digest              => $self->wants_digest
                }
            );
            unless ($transport) {
                my $logger = Koha::Logger->get;
                $logger->warn( $self->message_name
                        . ' has no transport with '
                        . $_->message_transport_type
                        . ' (digest: '
                        . ( $self->wants_digest ? 'yes' : 'no' )
                        . ').' );
            }
            $self->{'_message_transport_types'}->{ $_->message_transport_type } =
                $transport ? $transport->letter_code : ' ';
        } Koha::Patron::MessagePreference::Transport::Preferences->search(
            {
                borrower_message_preference_id => $self->borrower_message_preference_id,
            }
        )->as_list;
        return $self->{'_message_transport_types'} || {};
    } else {
        $self->_set_message_transport_types(@_);
        return $self;
    }
}

=head3 mtt_deliverable

$preference->mtt_deliverable('sms'[, $borrowernumer]);

Returns true if given message transport type can be used to deliver message to
patron.

By default, uses the borrowernumber bound to C<$preference>, but this may be
overridden by providing optional C<$borrowernumber> parameter.

=cut

sub mtt_deliverable {
    my ( $self, $mtt, $borrowernumber ) = @_;

    $borrowernumber //= $self->borrowernumber;

    return 0 unless ($borrowernumber);

    my $patron = Koha::Patrons->find( $self->borrowernumber );

    return (
        ( $mtt eq 'email' and $patron->notice_email_address )         # No email address
            or ( $mtt eq 'sms' and $patron->smsalertnumber )          # No SMS number
            or ( $mtt eq 'itiva'
            and C4::Context->preference('TalkingTechItivaPhoneNotification')
            )    # Notice is handled by TalkingTech_itiva_outbound.pl
            or ( $mtt eq 'phone' and $patron->phone )
        )        # No phone number to call
        ? 1 : 0;
}

=head3 set

$preference->set({
    message_transport_types => ['sms', 'phone'],
    wants_digest => 0,
})->store;

Sets preference object values and additionally message_transport_types if given.

=cut

sub set {
    my ( $self, $params ) = @_;

    my $mtt = $params->{'message_transport_types'};
    delete $params->{'message_transport_types'};

    $self->SUPER::set($params) if $params;
    if ($mtt) {
        $self->message_transport_types($mtt);
    }

    return $self;
}

=head3 store

Makes a validation before actual Koha::Object->store so that proper exceptions
can be thrown. See C<validate()> for documentation about exceptions.

=cut

sub store {
    my $self = shift;

    $self->validate->SUPER::store(@_);

    # store message transport types
    if ( exists $self->{'_message_transport_types'} ) {
        Koha::Patron::MessagePreference::Transport::Preferences->search(
            {
                borrower_message_preference_id => $self->borrower_message_preference_id,
            }
        )->delete;
        foreach my $type ( keys %{ $self->{'_message_transport_types'} } ) {
            Koha::Patron::MessagePreference::Transport::Preference->new(
                {
                    borrower_message_preference_id => $self->borrower_message_preference_id,
                    message_transport_type         => $type,
                }
            )->store;
        }
    }

    return $self;
}

=head3 validate

Makes a basic validation for object.

Returns Koha::Patron::MessagePreference object, or throws and exception.

=cut

sub validate {
    my ($self) = @_;

    if ( $self->borrowernumber && $self->categorycode ) {
        Koha::Exceptions::TooManyParameters->throw(
            error => 'Both borrowernumber and category given, only one accepted',
        );
    }
    if ( !$self->borrowernumber && !$self->categorycode ) {
        Koha::Exceptions::MissingParameter->throw(
            error => 'borrowernumber or category required, none given',
        );
    }
    if ( $self->borrowernumber ) {
        Koha::Exceptions::Patron::NotFound->throw(
            error => 'Patron not found.',
        ) unless Koha::Patrons->find( $self->borrowernumber );
    }
    if ( $self->categorycode ) {
        Koha::Exceptions::Patron::Category::NotFound->throw(
            error => 'Category not found.',
        ) unless Koha::Patron::Categories->find( $self->categorycode );
    }

    if ( !$self->in_storage ) {
        my $previous = Koha::Patron::MessagePreferences->search(
            {
                borrowernumber       => $self->borrowernumber,
                categorycode         => $self->categorycode,
                message_attribute_id => $self->message_attribute_id,
            }
        );
        if ( $previous->count ) {
            Koha::Exceptions::DuplicateObject->throw(
                error => 'A preference for this borrower/category and' . ' message_attribute_id already exists',
            );
        }
    }

    my $attr = Koha::Patron::MessagePreference::Attributes->find( $self->message_attribute_id );
    unless ($attr) {
        Koha::Exceptions::Patron::MessagePreference::AttributeNotFound->throw(
            error                => 'Message attribute with id ' . $self->message_attribute_id . ' not found',
            message_attribute_id => $self->message_attribute_id,
        );
    }
    if ( defined $self->days_in_advance ) {
        if ( $attr && $attr->takes_days == 0 ) {
            Koha::Exceptions::Patron::MessagePreference::DaysInAdvanceNotAvailable->throw(
                error        => 'days_in_advance cannot be defined for ' . $attr->message_name . '.',
                message_name => $attr->message_name,
            );
        } elsif ( $self->days_in_advance < 0 || $self->days_in_advance > 30 ) {
            Koha::Exceptions::Patron::MessagePreference::DaysInAdvanceOutOfRange->throw(
                error        => 'days_in_advance has to be a value between 0-30 for ' . $attr->message_name . '.',
                message_name => $attr->message_name,
                min          => 0,
                max          => 30,
            );
        }
    }
    if ( defined $self->wants_digest ) {
        my $transports = Koha::Patron::MessagePreference::Transports->search(
            {
                message_attribute_id => $self->message_attribute_id,
                is_digest            => $self->wants_digest ? 1 : 0,
            }
        );
        unless ( $transports->count ) {
            if ( !$self->wants_digest ) {
                Koha::Exceptions::Patron::MessagePreference::DigestRequired->throw(
                    error        => 'Digest must be selected for ' . $attr->message_name . '.',
                    message_name => $attr->message_name
                );
            } else {
                Koha::Exceptions::Patron::MessagePreference::DigestNotAvailable->throw(
                    error        => 'Digest cannot be selected for ' . $attr->message_name . '.',
                    message_name => $attr->message_name
                );
            }
        }
    }

    return $self;
}

sub _set_message_transport_types {
    my $self = shift;

    return unless $_[0];

    $self->{'_message_transport_types'} = undef;
    my $types = ref $_[0] eq "ARRAY" ? $_[0] : [@_];
    return unless $types;
    $self->_validate_message_transport_types( { message_transport_types => $types } );
    foreach my $type (@$types) {
        unless ( exists $self->{'_message_transport_types'}->{$type} ) {
            my $transport = Koha::Patron::MessagePreference::Transports->search(
                {
                    message_attribute_id   => $self->message_attribute_id,
                    message_transport_type => $type
                }
            )->next;
            unless ($transport) {
                Koha::Exceptions::Patron::MessagePreference::NoTransportType->throw(
                    error          => 'No transport configured for ' . $self->message_name . " transport type $type.",
                    message_name   => $self->message_name,
                    transport_type => $type,
                );
            }
            if ( defined $self->borrowernumber ) {
                if ( !$self->mtt_deliverable($type) ) {
                    Koha::Exceptions::Patron::MessagePreference::EmailAddressRequired->throw(
                        error        => 'Patron has not set email address, ' . 'cannot use email as message transport',
                        message_name => $self->message_name,
                        borrowernumber => $self->borrowernumber,
                    ) if $type eq 'email';
                    Koha::Exceptions::Patron::MessagePreference::TalkingTechItivaPhoneNotificationRequired->throw(
                        error => 'System preference TalkingTechItivaPhoneNotification disabled'
                            . 'cannot use itiva as message transport',
                        message_name   => $self->message_name,
                        borrowernumber => $self->borrowernumber,
                    ) if $type eq 'itiva';
                    Koha::Exceptions::Patron::MessagePreference::PhoneNumberRequired->throw(
                        error          => 'Patron has not set phone number' . 'cannot use phone as message transport',
                        message_name   => $self->message_name,
                        borrowernumber => $self->borrowernumber,
                    ) if $type eq 'phone';
                    Koha::Exceptions::Patron::MessagePreference::SMSNumberRequired->throw(
                        error          => 'Patron has not set SMS number' . 'cannot use sms as message transport',
                        message_name   => $self->message_name,
                        borrowernumber => $self->borrowernumber,
                    ) if $type eq 'sms';
                }
            }
            $self->{'_message_transport_types'}->{$type} = $transport->letter_code;
        }
    }
    return $self;
}

sub _validate_message_transport_types {
    my ( $self, $params ) = @_;

    if ( ref($params) eq 'HASH' && $params->{'message_transport_types'} ) {
        if ( ref( $params->{'message_transport_types'} ) ne 'ARRAY' ) {
            $params->{'message_transport_types'} = [ $params->{'message_transport_types'} ];
        }
        my $types = $params->{'message_transport_types'};

        foreach my $type ( @{$types} ) {
            unless ( Koha::Patron::MessagePreference::Transport::Types->find( { message_transport_type => $type } ) ) {
                Koha::Exceptions::Patron::MessagePreference::Transport::TypeNotFound->throw(
                    error          => "Message transport type '$type' does not exist",
                    transport_type => $type,
                );
            }
        }
        return $types;
    }
}

=head3 type

=cut

sub _type {
    return 'BorrowerMessagePreference';
}

=head1 AUTHOR

Lari Taskula <lari.taskula@hypernova.fi>

=cut

1;
