#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper;
use XML::LibXML;
use File::Copy "cp";

$XML::LibXML::skipXMLDeclaration = 1;
$XML::LibXML::setTagCompression = 1;

my $global_ref_conf = '/etc/koha/sites/prosentient/koha-conf.xml';

my $base_dir = '/etc/koha/sites';
my $opened = opendir(my $dh, $base_dir);
if ($opened){
    my @dirs = sort readdir($dh);
    DIR: foreach my $dir (@dirs){
        next if $dir eq '.' or $dir eq '..';
        my $koha_conf = "$base_dir/$dir/koha-conf.xml";
        eval {
            fixup_instance({ koha_conf => $koha_conf, });
        };
        if ($@){
            die "Error fixing instance ($koha_conf):\n$@";
        }
    }
}

sub fixup_instance {
    my ($args) = @_;
    my $koha_conf = $args->{koha_conf};
    if ( $koha_conf && -f $koha_conf ){
        my $doc = XML::LibXML->load_xml(
            location => $koha_conf,
        );
        my ($fixup,$errors) = fixup_doc({
            doc => $doc,
        });
        if ($fixup){
            warn "$koha_conf\n";
            foreach my $error (@$errors){
                warn "\t$error\n";
            }
            my $xml = $doc->toString(0);
            #NOTE: Backup original file
            my $backup_file = $koha_conf . ".orig." . time() . '~';
            my $copied = cp($koha_conf,$backup_file);
            if ($copied){
                warn "Backed up $koha_conf to $backup_file\n";
                if ($xml){
                    my $new_koha_conf = "$koha_conf";
                    my $file_open = open(my $fh,'>',$new_koha_conf);
                    if ($file_open){
                        print $fh $xml;
                        close($fh);
                    }
                }
            }
        }
    }

}

sub fixup_doc {
    my ($args) = @_;
    my $rv = 0;
    my @doc_errors = ();
    my $doc = $args->{doc};
    if ($doc){
        my @results = ();
        push(@results, fixup_elasticsearch_cxn_pool({ doc => $doc }) );
        push(@results, fixup_elasticsearch_trace_to({ doc => $doc }) );
        push(@results, fixup_comments({ doc => $doc }) );
        push(@results, fixup_plack_max_requests({ doc => $doc }) );
        push(@results, fixup_ttf({ doc => $doc }) );
        push(@results, fixup_lockdir({ doc => $doc }) );
        push(@results, fixup_queryparser_config({ doc => $doc }) );
        push(@results, fixup_plugin_repos({ doc => $doc }) );
        push(@results, fixup_koha_xslt_security({ doc => $doc }) );
        push(@results, fixup_smtp_server({ doc => $doc }) );
        push(@results, fixup_bcrypt({ doc => $doc }) );
        foreach my $result (@results) {
            if ( $result->{rv} ){
                $rv  = $rv + $result->{rv};
                if ( $result->{errors} ){
                    push(@doc_errors,@{$result->{errors}});
                }
            }
        }
    }
    return ($rv,\@doc_errors);
}

sub _get_ref_doc {
    my $ref_doc = XML::LibXML->load_xml(
        location => $global_ref_conf,
    );
    return $ref_doc;
}

sub fixup_blank {
    my ($args) = @_;
    my $rv = 0;
    my @errors = ();
    my $doc = $args->{doc};
    if ($doc){
    }
    return { rv => $rv, errors => \@errors };
}

sub fixup_elasticsearch_cxn_pool {
    my ($args) = @_;
    my $rv = 0;
    my @errors = ();
    my $doc = $args->{doc};
    if ($doc){
        my $elasticsearch = $doc->findnodes('//elasticsearch')->shift;
        if ($elasticsearch){
            my $cxn_pool = $elasticsearch->findnodes('./cxn_pool')->shift;
            if ( ! $cxn_pool ){
                push(@errors,(caller(0))[3] . ": elasticsearch missing cxn_pool");
                $rv = 1;
                my $index_name_comment = $elasticsearch->findnodes('./index_name/following-sibling::comment()')->shift;
                if ($index_name_comment){
                    my $cxn_pool = XML::LibXML::Element->new('cxn_pool');
                    $cxn_pool->appendTextNode('Static');
                    $elasticsearch->insertAfter($cxn_pool,$index_name_comment);
                    $elasticsearch->insertAfter(XML::LibXML::Text->new("\n     "),$index_name_comment);
                    $elasticsearch->insertAfter(XML::LibXML::Comment->new(" See https://metacpan.org/pod/Search::Elasticsearch#cxn_pool "),$index_name_comment);
                    $elasticsearch->insertAfter(XML::LibXML::Text->new("\n\n     "),$index_name_comment);
                }
            }
        }
    }
    return { rv => $rv, errors => \@errors };
}

sub fixup_elasticsearch_trace_to {
    my ($args) = @_;
    my $rv = 0;
    my @errors = ();
    my $doc = $args->{doc};
    if ($doc){
        my $elasticsearch = $doc->findnodes('//elasticsearch')->shift;
        if ($elasticsearch){
            my $trace_to_comments = $elasticsearch->findnodes('./comment()[contains(.,"trace_to")]')->shift;
            if ( ! $trace_to_comments ){
                push(@errors,(caller(0))[3] . ": elasticsearch missing trace_to comments");
                $rv = 1;
                my $cxn_pool = $elasticsearch->findnodes('./cxn_pool')->shift;
                if ($cxn_pool){
                    $elasticsearch->insertAfter(XML::LibXML::Comment->new(" <trace_to>Stderr</trace_to> "),$cxn_pool);
                    $elasticsearch->insertAfter(XML::LibXML::Text->new("\n     "),$cxn_pool);
                    $elasticsearch->insertAfter(XML::LibXML::Comment->new(" See https://metacpan.org/pod/Search::Elasticsearch#trace_to "),$cxn_pool);
                    $elasticsearch->insertAfter(XML::LibXML::Text->new("\n     "),$cxn_pool);
                }
            }
        }
    }
    return { rv => $rv, errors => \@errors };
}

sub fixup_comments {
    my ($args) = @_;
    my $rv = 0;
    my @errors = ();
    my $doc = $args->{doc};
    if ($doc){
        my @comments = $doc->findnodes('/yazgfs/config/comment()[contains(.," Accessible directory from the staff client")]');
        if (@comments){
            push(@errors,(caller(0))[3] . ": comment uses staff client instead of staff interface");
            $rv = 1;
            foreach my $comment (@comments){
                my $text = $comment->textContent;
                $text =~ s/staff client/staff interface/;
                $comment->setData($text);
            }
        }
    }
    return { rv => $rv, errors => \@errors };
}

sub fixup_plack_max_requests {
    my ($args) = @_;
    my $rv = 0;
    my @errors = ();
    my $doc = $args->{doc};
    if ($doc){
        my $plack_max_requests = $doc->findnodes('//plack_max_requests/text()')->shift;
        if ($plack_max_requests){
            if ( $plack_max_requests eq '25' ){
                push(@errors,(caller(0))[3] . ": plack_max_requests is 25");
                $rv = 1;
                $plack_max_requests->setData(50);
            }
        }
    }
    return { rv => $rv, errors => \@errors };
}

sub fixup_lockdir {
    my ($args) = @_;
    my $rv = 0;
    my @errors = ();
    my $doc = $args->{doc};
    if ($doc){
        my @elements_found = $doc->findnodes('//lockdir');
        if ( ! @elements_found ){
            push(@errors,(caller(0))[3] . ": missing lockdir");
            $rv = 1;
            my $zebra_lockdir = $doc->findnodes('//zebra_lockdir')->shift;
            if ($zebra_lockdir){
                my $lockdir = $zebra_lockdir->cloneNode(1);
                if ($lockdir){
                    $lockdir->setNodeName('lockdir');
                    $zebra_lockdir->parentNode->insertAfter($lockdir,$zebra_lockdir);
                    $zebra_lockdir->parentNode->insertAfter(XML::LibXML::Text->new("\n "),$zebra_lockdir);
                }
            }
        }
    }
    return { rv => $rv, errors => \@errors };
}

sub fixup_queryparser_config {
    my ($args) = @_;
    my $rv = 0;
    my @errors = ();
    my $doc = $args->{doc};
    if ($doc){
        my @elements_found = $doc->findnodes('//queryparser_config');
        if (@elements_found){
            push(@errors,(caller(0))[3] . ": still contains queryparser_config");
            $rv = 1;
            foreach my $element (@elements_found){
                my $nextnode = $element->nextSibling();
                if ($nextnode && $nextnode->data eq "\n "){
                    $element->parentNode->removeChild($nextnode);
                }
                $element->parentNode->removeChild($element);
            }
        }
    }
    return { rv => $rv, errors => \@errors };
}

sub fixup_ttf {
    my ($args) = @_;
    my $rv = 0;
    my @errors = ();
    my $doc = $args->{doc};
    if ($doc){
        my @elements_found = $doc->findnodes('//ttf/font[text()="/usr/share/fonts/truetype/ttf-dejavu/DejaVuSerif.ttf"]');
        if ( @elements_found ){
            push(@errors,(caller(0))[3] . ": incorrect ttf");
            $rv = 1;
            my $ref_doc = _get_ref_doc();
            if ($ref_doc){
                my $ref_ttf = $ref_doc->findnodes('//ttf')->shift->cloneNode(1);
                if ($ref_ttf){
                    my $ttf = $doc->findnodes('//ttf')->shift;
                    if ($ttf){
                        $ttf->replaceNode($ref_ttf);
                    }
                }
            }
        }
    }
    return { rv => $rv, errors => \@errors };
}

sub fixup_plugin_repos {
    my ($args) = @_;
    my $rv = 0;
    my @errors = ();
    my $doc = $args->{doc};
    if ($doc){
        my @elements_found = $doc->findnodes('//plugin_repos');
        if ( ! @elements_found ){
            push(@errors,(caller(0))[3] . ": missing plugin_repos");
            $rv = 1;
            my $ref_doc = _get_ref_doc();
            if ($ref_doc){
                my $plugin_repos = $ref_doc->findnodes('//plugin_repos')->shift->cloneNode(1);
                if ($plugin_repos){
                    my $strict_sql_modes = $doc->findnodes('//strict_sql_modes')->shift;
                    if ($strict_sql_modes){
                        $strict_sql_modes->parentNode->insertAfter($plugin_repos,$strict_sql_modes);
                        $strict_sql_modes->parentNode->insertAfter(XML::LibXML::Text->new("\n "),$strict_sql_modes);
                    }
                }
            }
        }
    }
    return { rv => $rv, errors => \@errors };
}

sub fixup_koha_xslt_security {
    my ($args) = @_;
    my $rv = 0;
    my @errors = ();
    my $doc = $args->{doc};
    if ($doc){
        my @elements_found = $doc->findnodes('//koha_xslt_security');
        if ( ! @elements_found ){
            push(@errors,(caller(0))[3] . ": missing koha_xslt_security");
            $rv = 1;
            my $ref_doc = _get_ref_doc();
            if ($ref_doc){
                my $clone_node = $ref_doc->findnodes('//koha_xslt_security')->shift->cloneNode(1);
                if ($clone_node){
                    my $anchor = $doc->findnodes('//plugin_repos')->shift;
                    if ($anchor){
                        $anchor->parentNode->insertAfter($clone_node,$anchor);
                        $anchor->parentNode->insertAfter(XML::LibXML::Text->new("\n\n "),$anchor);
                    }
                }
            }
        }
    }
    return { rv => $rv, errors => \@errors };
}

sub fixup_smtp_server {
    my ($args) = @_;
    my $rv = 0;
    my @errors = ();
    my $doc = $args->{doc};
    if ($doc){
        my @elements_found = $doc->findnodes('//smtp_server');
        if ( ! @elements_found ){
            push(@errors,(caller(0))[3] . ": missing smtp_server");
            $rv = 1;
            my $ref_doc = _get_ref_doc();
            if ($ref_doc){
                my $clone_node = $ref_doc->findnodes('//smtp_server')->shift->cloneNode(1);
                if ($clone_node){
                    my $anchor = $doc->findnodes('//koha_xslt_security')->shift;
                    if ($anchor){
                        $anchor->parentNode->insertAfter($clone_node,$anchor);
                        $anchor->parentNode->insertAfter(XML::LibXML::Text->new("\n\n "),$anchor);
                    }
                }
            }
        }
    }
    return { rv => $rv, errors => \@errors };
}

sub fixup_bcrypt {
    my ($args) = @_;
    my $rv = 0;
    my @errors = ();
    my $doc = $args->{doc};
    if ($doc){
        my @bcrypt = $doc->findnodes('//bcrypt_settings');
        if ( ! @bcrypt ){
            push(@errors,(caller(0))[3] . ": missing bcrypt_settings");
            $rv = 1;
            my @timezones = $doc->findnodes('//timezone');
            if (@timezones){
                my $timezone = $timezones[-1];
                if ($timezone){
                    my $node = XML::LibXML::Element->new('bcrypt_settings');
                    my $command = q#htpasswd -bnBC 10 "" password | tr -d ':\n' | sed 's/$2y/$2a/'#;
                    my $bcrypt_setting = `$command`;
                    $node->appendText($bcrypt_setting);
                    $timezone->parentNode->insertAfter($node,$timezone);
                    $timezone->parentNode->insertAfter(XML::LibXML::Text->new(" "),$timezone);
                    $timezone->parentNode->insertAfter(XML::LibXML::Text->new("\n"),$timezone);
                    $timezone->parentNode->insertAfter(XML::LibXML::Comment->new(" This is the bcrypt settings used to generated anonymized content "),$timezone);
                    $timezone->parentNode->insertAfter(XML::LibXML::Text->new("\n\n "),$timezone);
                }
            }
        }
    }
    return { rv => $rv, errors => \@errors };
}
