package OpenILS::WWW::PrintTemplate;
use strict; use warnings;
use Apache2::Const -compile => 
    qw(OK FORBIDDEN NOT_FOUND HTTP_INTERNAL_SERVER_ERROR HTTP_BAD_REQUEST);
use Apache2::RequestRec;
use CGI;
use HTML::Defang;
use DateTime;
use DateTime::Format::ISO8601;
use Unicode::Normalize;
use OpenSRF::Utils::JSON;
use OpenSRF::System;
use OpenSRF::Utils::SettingsClient;
use OpenILS::Utils::CStoreEditor q/:funcs/;
use OpenSRF::Utils::Logger q/$logger/;
use OpenILS::Application::AppUtils;
use OpenILS::Utils::DateTime qw/:datetime/;

my $U = 'OpenILS::Application::AppUtils';
my $helpers;

my $bs_config;
my $enable_cache; # Enable process-level template caching
sub import {
    $bs_config = shift;
    $enable_cache = shift;
}

my $init_complete = 0;
sub child_init {
    $init_complete = 1;

    OpenSRF::System->bootstrap_client(config_file => $bs_config);
    OpenILS::Utils::CStoreEditor->init;
    return Apache2::Const::OK;
}

# HTML scrubber
# https://metacpan.org/pod/HTML::Defang
my $defang = HTML::Defang->new;

sub handler {
    my $r = shift;
    my $cgi = CGI->new;

    child_init() unless $init_complete;

    my $auth = $cgi->param('ses') || 
        $cgi->cookie('eg.auth.token') || $cgi->cookie('ses');

    my $e = new_editor(authtoken => $auth);

    # Requires staff login
    return Apache2::Const::FORBIDDEN 
        unless $e->checkauth && $e->requestor->wsid;

    # Let pcrud handle the authz
    $e->personality('open-ils.pcrud');

    my $tmpl_owner = $cgi->param('template_owner') || $e->requestor->ws_ou;
    my $tmpl_locale = $cgi->param('template_locale') || 'en-US';
    my $tmpl_id = $cgi->param('template_id');
    my $tmpl_name = $cgi->param('template_name');
    my $tmpl_data = $cgi->param('template_data');
    my $client_timezone = $cgi->param('client_timezone');

    return Apache2::Const::HTTP_BAD_REQUEST unless $tmpl_name || $tmpl_id;

    my $template = 
        find_template($e, $tmpl_id, $tmpl_name, $tmpl_locale, $tmpl_owner)
        or return Apache2::Const::NOT_FOUND;

    my $data;
    eval { $data = OpenSRF::Utils::JSON->JSON2perl($tmpl_data); };
    if ($@) {
        $logger->error("Invalid JSON in template compilation: $tmpl_data");
        return Apache2::Const::HTTP_BAD_REQUEST;
    }

    my ($staff_org) = $U->fetch_org_unit($e->requestor->ws_ou);

    my $output = '';
    my $tt = Template->new;
    my $tmpl = $template->template;

    my $context = {
        template_locale => $tmpl_locale,
        client_timezone => $client_timezone,
        staff => $e->requestor,
        staff_org => $staff_org,
        staff_org_timezone => get_org_timezone($e, $staff_org->id),
        helpers => $helpers,
        template_data => $data
    };

    my $stat = $tt->process(\$tmpl, $context, \$output);

    if ($stat) { # OK
        my $ctype = $template->content_type;
        if ($ctype eq 'text/html') {
            $output = $defang->defang($output); # Scrub the HTML
        }
        # TODO
        # client current expects content type to only contain type.
        # $r->content_type("$ctype; encoding=utf8");
        $r->content_type($ctype);
        $r->print($output);
        return Apache2::Const::OK;

    } else {

        (my $error = $tt->error) =~ s/\n/ /og;
        $logger->error("Error processing print template: $error");
        return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
    }
}

my %org_timezone_cache;
sub get_org_timezone {
    my ($e, $org_id) = @_;

    if (!$org_timezone_cache{$org_id}) {

        # open-ils.auth call required since our $e is in pcrud mode.
        my $value = $U->simplereq(
            'open-ils.actor',
            'open-ils.actor.ou_setting.ancestor_default', 
            $org_id, 'lib.timezone');

        $org_timezone_cache{$org_id} = $value ? $value->{value} : 
            DateTime->now(time_zone => 'local')->time_zone->name;
    }

    return $org_timezone_cache{$org_id};
}


# Find the template closest to the specific org unit owner.
my %template_cache;
sub find_template {
    my ($e, $template_id, $name, $locale, $owner) = @_;

    if ($template_id) {
        # Requesting by ID, generally used for testing, 
        # always pulls the latest value and ignores the active flag
        return $e->retrieve_config_print_template($template_id);
    }

    return  $template_cache{$owner}{$name}{$locale}
        if  $enable_cache &&
            $template_cache{$owner} && 
            $template_cache{$owner}{$name} &&
            $template_cache{$owner}{$name}{$locale};

    while ($owner) {
        my ($org) = $U->fetch_org_unit($owner); # cached in AppUtils
        
        my $template = $e->search_config_print_template({
            name => $name, 
            locale => $locale, 
            owner => $org->id,
            active => 't'
        })->[0];

        if ($template) {

            if ($enable_cache) {
                $template_cache{$owner} = {} unless $template_cache{$owner};
                $template_cache{$owner}{$name} = {} 
                    unless $template_cache{$owner}{$name};
                $template_cache{$owner}{$name}{$locale} = $template;
            }

            return $template;
        }

        $owner = $org->parent_ou;
    }

    return undef;
}

# Utility / helper functions passed into every template

$helpers = {

    # turns a date w/ optional timezone modifier into something 
    # TT can understand
    format_date => sub {
        my $date = shift;
        my $tz = shift;

        $date = DateTime::Format::ISO8601->new->parse_datetime(clean_ISO8601($date));
        $date->set_time_zone($tz) if $tz;

        return sprintf(
            "%0.2d:%0.2d:%0.2d %0.2d-%0.2d-%0.4d",
            $date->hour,
            $date->minute,
            $date->second,
            $date->day,
            $date->month,
            $date->year
        );
    },

    current_date => sub {
        my $tz = shift || 'local';
        my $date = DateTime->now(time_zone => $tz);
        return $helpers->{format_date}->($date);
    }
};




1;
