package Koha::REST::V1::OAuth::Client;

# 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::Auth::Client::OAuth;

use Mojo::Base 'Mojolicious::Controller';
use Mojo::URL;
use Scalar::Util qw(blessed);
use Try::Tiny;
use Koha::Token;
use URI::Escape qw(uri_escape_utf8);
use Koha::Session;
use Koha::Auth::Identity::Referer;

=head1 NAME

Koha::REST::V1::OAuth::Client - Controller library for handling OAuth2-related login attempts

=head1 API

=head2 Methods

=head3 login

Controller method handling login requests

=cut

sub login {
    my $c = shift->openapi->valid_input or return;

    my $provider  = $c->param('provider_code');
    my $interface = $c->param('interface');

    my $provider_config = $c->oauth2->providers->{$provider};

    my $uri;
    my $redirect_url;

    if ( $interface eq 'opac' ) {
        $redirect_url = C4::Context->preference('OPACBaseURL') . '/api/v1/public/oauth/login/';
        if ( C4::Context->preference('OpacPublic') ) {
            $uri = '/cgi-bin/koha/opac-user.pl';
        } else {
            $uri = '/cgi-bin/koha/opac-main.pl';
        }
    } else {
        $redirect_url = C4::Context->preference('staffClientBaseURL') . '/api/v1/oauth/login/';
        $uri          = '/cgi-bin/koha/mainpage.pl';
    }

    my $current_session;
    my $current_session_cookie = $c->req->cookie('CGISESSID');
    if ($current_session_cookie) {
        my $current_session_id = $current_session_cookie->value;
        $current_session = Koha::Session->get_session( { sessionID => $current_session_id } );
    }

    unless ( $provider_config && $provider_config->{authorize_url} ) {
        my $error = "No configuration found for your provider";
        return $c->redirect_to( $uri . "?auth_error=$error" );
    }

    unless ( $provider_config->{authorize_url} =~ /response_type=code/ ) {
        my $authorize_url = Mojo::URL->new( $provider_config->{authorize_url} );
        $authorize_url->query->append( response_type => 'code' );
        $provider_config->{authorize_url} = $authorize_url->to_string;
    }

    # Determine if it is a callback request, or the initial
    my $is_callback = $c->param('error_description') || $c->param('error') || $c->param('code');

    my $state;

    if ($is_callback) {

        # callback, check CSRF token
        unless (
            Koha::Token->new->check_csrf(
                {
                    session_id => $c->req->cookie('CGISESSID')->value,
                    token      => $c->param('state'),
                }
            )
            )
        {
            my $error = "wrong_csrf_token";
            return $c->redirect_to( $uri . "?auth_error=$error" );
        }
    } else {

        # initial request, generate CSRF token
        $state = Koha::Token->new->generate_csrf( { session_id => $c->req->cookie('CGISESSID')->value } );

        Koha::Auth::Identity::Referer->store_referer(
            {
                referer   => $c->req->headers->referer,
                interface => $interface,
                session   => $current_session,
            }
        );
    }

    return $c->oauth2->get_token_p(
        $provider => {
            ( !$is_callback ? ( state => $state ) : () ), redirect_uri => $redirect_url . $provider . "/" . $interface
        }
    )->then(
        sub {
            return unless my $response = shift;

            try {
                my ( $patron, $mapped_data, $domain ) = Koha::Auth::Client::OAuth->new->get_user(
                    {
                        provider  => $provider,
                        data      => $response,
                        interface => $interface,
                        config    => $c->oauth2->providers->{$provider}
                    }
                );

                if ( !$patron ) {
                    $patron = $c->auth->register(
                        {
                            data      => $mapped_data,
                            domain    => $domain,
                            interface => $interface
                        }
                    );
                }

                my $session_id =
                    $c->auth->session( { patron => $patron, interface => $interface, provider => $provider } );

                $c->cookie( CGISESSID => $session_id, { path => "/" } );

                my $target_uri = Koha::Auth::Identity::Referer->get_referer(
                    {
                        session => $current_session,
                    }
                );
                if ($target_uri) {
                    $uri = $target_uri;
                }
                $c->redirect_to($uri);
            } catch {
                my $error = $_;
                $c->app->log->error($error);

                # TODO: Review behavior
                if ( blessed $error ) {
                    if ( $error->isa('Koha::Exceptions::Auth::Unauthorized') ) {
                        $error = "User cannot access this resource";
                    }
                    if ( $error->isa('Koha::Exceptions::Auth::NoValidDomain') ) {
                        $error = "No configuration found for your email domain";
                    }
                }

                $error = uri_escape_utf8($error);

                $c->redirect_to( $uri . "?auth_error=$error" );
            };
        }
    )->catch(
        sub {
            my $error = shift;
            $c->app->log->error($error);
            $error = uri_escape_utf8($error);
            $c->redirect_to( $uri . "?auth_error=$error" );
        }
    )->wait;
}

1;
