package Koha::CurbsidePickup;

# 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 Carp;

use Koha::Database;

use base qw(Koha::Object);

use C4::Circulation        qw( CanBookBeIssued AddIssue );
use C4::Members::Messaging qw( GetMessagingPreferences );
use C4::Letters            qw( GetPreparedLetter EnqueueLetter );
use Koha::Calendar;
use Koha::DateUtils qw( dt_from_string );
use Koha::Patron;
use Koha::Library;
use Koha::CurbsidePickupIssues;
use Koha::Exceptions::CurbsidePickup;

=head1 NAME

Koha::CurbsidePickup - Koha Curbside Pickup Object class

=head1 API

=head2 Class methods

=cut

=head3 new

=cut

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

    my $policy =
        Koha::CurbsidePickupPolicies->find( { branchcode => $params->{branchcode} } );

    Koha::Exceptions::CurbsidePickup::NotEnabled->throw
        unless $policy && $policy->enabled;

    my $calendar = Koha::Calendar->new( branchcode => $params->{branchcode} );
    Koha::Exceptions::CurbsidePickup::LibraryIsClosed->throw
        if $calendar->is_holiday( $params->{scheduled_pickup_datetime} );

    if ( $policy->enable_waiting_holds_only ) {
        my $patron        = Koha::Patrons->find( $params->{borrowernumber} );
        my $waiting_holds = $patron->holds->waiting->search( { branchcode => $params->{branchcode} } );

        Koha::Exceptions::CurbsidePickup::NoWaitingHolds->throw
            unless $waiting_holds->count;
    }
    my $existing_curbside_pickups = Koha::CurbsidePickups->search(
        {
            branchcode         => $params->{branchcode},
            borrowernumber     => $params->{borrowernumber},
            delivered_datetime => undef,
        }
    )->filter_by_scheduled_today;
    Koha::Exceptions::CurbsidePickup::TooManyPickups->throw(
        branchcode     => $params->{branchcode},
        borrowernumber => $params->{borrowernumber}
    ) if $existing_curbside_pickups->count;

    my $is_valid = $policy->is_valid_pickup_datetime( $params->{scheduled_pickup_datetime} );
    unless ($is_valid) {
        my $error = @{ $is_valid->messages }[0]->message;
        Koha::Exceptions::CurbsidePickup::NoMatchingSlots->throw
            if $error eq 'no_matching_slots';
        Koha::Exceptions::CurbsidePickup::NoMorePickupsAvailable->throw
            if $error eq 'no_more_available';
        Koha::Exceptions->throw("Error message must raise the appropriate exception");
    }

    return $self->SUPER::new($params);
}

=head3 notify_new_pickup

$pickup->notify_new_pickup

Will notify the patron that the pickup has been created.
Letter 'NEW_CURBSIDE_PICKUP will be used', and depending on 'Hold_Filled' configuration.

=cut

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

    my $patron = $self->patron;

    my $library = $self->library;

    $patron->queue_notice(
        {
            letter_params => {
                module         => 'reserves',
                letter_code    => 'NEW_CURBSIDE_PICKUP',
                borrowernumber => $patron->borrowernumber,
                branchcode     => $self->branchcode,
                tables         => {
                    'branches'  => $library->unblessed,
                    'borrowers' => $patron->unblessed,
                },
                substitute => {
                    curbside_pickup => $self,
                }
            },
            message_name => 'Hold_Filled'
        }
    );
}

=head3 checkouts

Return the checkouts linked to this pickup

=cut

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

    my @pi = Koha::CurbsidePickupIssues->search( { curbside_pickup_id => $self->id } )->as_list;

    my @checkouts = map { $_->checkout } @pi;
    @checkouts = grep { defined $_ } @checkouts;

    return @checkouts;
}

=head3 patron

Return the patron linked to this pickup

=cut

sub patron {
    my ($self) = @_;
    my $rs = $self->_result->borrowernumber;
    return unless $rs;
    return Koha::Patron->_new_from_dbic($rs);
}

=head3 staged_by_staff

Return the staff member that staged this pickup

=cut

sub staged_by_staff {
    my ($self) = @_;
    my $rs = $self->_result->staged_by;
    return unless $rs;
    return Koha::Patron->_new_from_dbic($rs);
}

=head3 library

Return the branch associated with this pickup

=cut

sub library {
    my ($self) = @_;
    my $rs = $self->_result->branchcode;
    return unless $rs;
    return Koha::Library->_new_from_dbic($rs);
}

=head3 mark_as_staged

Mark the pickup as staged

=cut

sub mark_as_staged {
    my ($self) = @_;
    my $staged_by = C4::Context->userenv ? C4::Context->userenv->{number} : undef;
    $self->set(
        {
            staged_datetime  => dt_from_string(),
            staged_by        => $staged_by,
            arrival_datetime => undef,
        }
    )->store;
}

=head3 mark_as_unstaged

Mark the pickup as unstaged

=cut

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

    $self->set(
        {
            staged_datetime  => undef,
            staged_by        => undef,
            arrival_datetime => undef,
        }
    )->store;
}

=head3 mark_patron_has_arrived

Set the arrival time of the patron

=cut

sub mark_patron_has_arrived {
    my ($self) = @_;
    $self->set(
        {
            arrival_datetime => dt_from_string(),
        }
    )->store;
}

=head3 mark_as_delivered

Mark the pickup as delivered. The waiting holds will be filled.

=cut

sub mark_as_delivered {
    my ($self)     = @_;
    my $patron     = $self->patron;
    my $holds      = $patron->holds;
    my $branchcode = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
    foreach my $hold ( $holds->as_list ) {
        if ( $hold->is_waiting && $branchcode && $hold->branchcode eq $branchcode ) {
            my ( $issuingimpossible, $needsconfirmation ) =
                C4::Circulation::CanBookBeIssued( $patron, $hold->item->barcode );

            unless ( keys %$issuingimpossible ) {
                my $issue = C4::Circulation::AddIssue(
                    $patron,
                    $hold->item->barcode,
                    undef, undef, undef, undef,
                    {
                        confirmations => [ grep { /^[A-Z_]+$/ } keys %{$needsconfirmation} ],
                        forced        => [ keys %{$issuingimpossible} ]
                    }
                );
                if ($issue) {
                    Koha::CurbsidePickupIssue->new(
                        {
                            curbside_pickup_id => $self->id,
                            issue_id           => $issue->id,
                            reserve_id         => $hold->id,
                        }
                    )->store();
                } else {
                    Koha::Exceptions->throw(
                        sprintf(
                            "Cannot checkout hold %s for patron %s: %s", $patron->id, $hold->id,
                            join( ", ", keys %$issuingimpossible )
                        )
                    );
                }
            }
        }
    }

    my $delivered_by = C4::Context->userenv ? C4::Context->userenv->{number} : undef;
    $self->arrival_datetime(dt_from_string) unless $self->arrival_datetime;
    $self->set(
        {
            delivered_datetime => dt_from_string(),
            delivered_by       => $delivered_by,
        }
    )->store;
}

=head3 status

Return the status of the pickup, can be 'to-be-staged', 'staged-and-ready', 'patron-is-outside' or 'delivered'.

=cut

sub status {
    my ($self) = @_;
    return
          !defined $self->staged_datetime    ? 'to-be-staged'
        : !defined $self->arrival_datetime   ? 'staged-and-ready'
        : !defined $self->delivered_datetime ? 'patron-is-outside'
        :                                      'delivered';
}

=head2 Internal methods

=head3 _type

=cut

sub _type {
    return 'CurbsidePickup';
}

1;
