#!/usr/bin/env perl
use strict;
use warnings;

our $VERSION = '1.1.0';

use 5.14.2;

use English qw( $PID );
use JSON::PP;
use JSON::RPC::Dispatch;
use Log::Any qw( $log );
use Log::Any::Adapter;
use POSIX;
use Plack::Builder;
use Plack::Response;
use Router::Simple::Declare;
use Try::Tiny;

BEGIN {
    $ENV{PERL_JSON_BACKEND} = 'JSON::PP';
    undef $ENV{LANGUAGE};
};

use Zonemaster::Backend::RPCAPI;
use Zonemaster::Backend::Config;
use Zonemaster::Backend::Metrics;

local $| = 1;

Log::Any::Adapter->set(
    '+Zonemaster::Backend::Log',
    log_level => $ENV{ZM_BACKEND_RPCAPI_LOGLEVEL},
    json => $ENV{ZM_BACKEND_RPCAPI_LOGJSON},
    stderr => 1
);

$SIG{__WARN__} = sub {
    $log->warning(map s/^\s+|\s+$//gr, map s/\n/ /gr, @_);
};

my $config = Zonemaster::Backend::Config->load_config();

Zonemaster::Backend::Metrics->setup($config->METRICS_statsd_host, $config->METRICS_statsd_port);
Zonemaster::Engine::init_engine();

builder {
    enable sub {
        my $app = shift;

        # Make sure we can connect to the database
        $config->new_DB();

        return $app;
    };
};

my $handler = Zonemaster::Backend::RPCAPI->new( { config => $config } );

my $router = router {
############## FRONTEND ####################
    connect "version_info" => {
        handler => $handler,
        action => "version_info"
    };

    # Experimental
    connect "system_versions" => {
        handler => $handler,
        action => "system_versions"
    };

    connect "profile_names" => {
        handler => $handler,
        action => "profile_names"
    };

    # Experimental
    connect "conf_profiles" => {
        handler => $handler,
        action => "conf_profiles"
    };

    connect "get_language_tags" => {
        handler => $handler,
        action => "get_language_tags"
    };

    # Experimental
    connect "conf_languages" => {
        handler => $handler,
        action => "conf_languages"
    };

    connect "get_host_by_name" => {
        handler => $handler,
        action => "get_host_by_name"
    };

    # Experimental
    connect "lookup_address_records" => {
        handler => $handler,
        action => "lookup_address_records"
    };

    connect "get_data_from_parent_zone" => {
        handler => $handler,
        action => "get_data_from_parent_zone"
    };

    # Experimental
    connect "lookup_delegation_data" => {
        handler => $handler,
        action => "lookup_delegation_data"
    };

    connect "start_domain_test" => {
        handler => $handler,
        action => "start_domain_test"
    };

    # Experimental
    connect "job_create" => {
        handler => $handler,
        action => "job_create"
    };

    connect "test_progress" => {
        handler => $handler,
        action => "test_progress"
    };

    # Experimental
    connect "job_status" => {
        handler => $handler,
        action => "job_status"
    };

    connect "get_test_params" => {
        handler => $handler,
        action => "get_test_params"
    };

    # Experimental
    connect "job_params" => {
        handler => $handler,
        action => "job_params"
    };

    connect "get_test_results" => {
        handler => $handler,
        action => "get_test_results"
    };

    # Experimental
    connect "job_results" => {
        handler => $handler,
        action => "job_results"
    };

    connect "get_test_history" => {
        handler => $handler,
        action => "get_test_history"
    };

    # Experimental
    connect "domain_history" => {
        handler => $handler,
        action => "domain_history"
    };

    connect "batch_status" => {
        handler => $handler,
        action => "batch_status"
    };
};

if ( $config->RPCAPI_enable_user_create or $config->RPCAPI_enable_add_api_user ) {
    $log->info('Enabling add_api_user method');
    $router->connect("add_api_user", {
        handler => $handler,
        action => "add_api_user"
    });
    $router->connect("user_create", {
        handler => $handler,
        action => "user_create"
    });
}

if ( $config->RPCAPI_enable_batch_create or $config->RPCAPI_enable_add_batch_job ) {
    $log->info('Enabling add_batch_job method');
    $router->connect("add_batch_job", {
        handler => $handler,
        action => "add_batch_job"
    });
    $router->connect("batch_create", {
        handler => $handler,
        action => "batch_create"
    });
}

my $dispatch = JSON::RPC::Dispatch->new(
    router => $router,
);

my $rpcapi_app = sub {
    my $env = shift;
    my $req = Plack::Request->new($env);
    my $res = {};
    my $content = {};
    my $json_error = '';
    try {
        my $json = $req->content;
        $content = decode_json($json);
    } catch {
        $json_error = (split /at \//, $_)[0];
    };

    if ($json_error eq '') {
        my $errors = $handler->jsonrpc_validate($content);
        if ($errors ne '') {
          $res = Plack::Response->new(200);
          $res->content_type('application/json');
          $res->body( encode_json($errors) );
          $res->finalize;
        } else {
            local $log->context->{rpc_method} = $content->{method};
            $res = $dispatch->handle_psgi($env, $env->{REMOTE_ADDR});
            my $status = Zonemaster::Backend::Metrics->code_to_status(decode_json(@{@$res[2]}[0])->{error}->{code});
            Zonemaster::Backend::Metrics::increment("zonemaster.rpcapi.requests.$content->{method}.$status");
            $res;
        }
    } else {
        $res = Plack::Response->new(200);
        $res->content_type('application/json');
        $res->body( encode_json({
                    jsonrpc => '2.0',
                    id => undef,
                    error => {
                        code => '-32700',
                        message => 'Invalid JSON was received by the server.',
                        data => "$json_error"
                    }}) );
        $res->finalize;

    }
};

builder {
    enable "Plack::Middleware::ReverseProxy";
    mount "/" => $rpcapi_app;
};
