package NGCP::Panel::Utils::DeviceBootstrap::ALE;

use strict;
use warnings;

use URI::Escape;
use Moo;
use Types::Standard qw(Str);
use JSON qw/encode_json decode_json/;
use MIME::Base64;
use Digest::MD5 qw/md5_hex/;
use TryCatch;

extends 'NGCP::Panel::Utils::DeviceBootstrap::VendorREST';

sub rpc_server_params{
    my $self = shift;
    my $cfg  = {
        proto    => 'https',
        host     => 'api.eds.al-enterprise.com',
        path     => 'api',
    };
    $self->{rpc_server_params} = $cfg;
    return $self->{rpc_server_params};
}

sub rest_prepare_request {
    my ($self, $action) = @_;
    my $c = $self->params->{c};
    my ($op_name, $url, $ret, $res, $data, $rc, $err);
    my $new_mac = $self->content_params->{mac};
    my $old_mac = $self->content_params->{mac_old};
    my $param_servername = $self->content_params->{server_name};
    my $param_uri = $self->content_params->{uri};
    my $token;

    $self->{rpc_server_params} //= $self->rpc_server_params;
    my $cfg = $self->{rpc_server_params};

    my $tx_id = $c->stash->{api_request_tx_id} //
                uc Data::UUID->create_str() =~ s/-//gr;

    $c->log->debug($self->to_log({ name   => 'ALE prepare request',
                                   tx_id  => $tx_id,
                                   action => $action }));

    # first, generate token ---------------------------------------------------
    $op_name = 'ALE generate token';
    $err = '';
    $url = "$$cfg{proto}://$$cfg{host}/$$cfg{path}/bp_user/generate/token";
    $c->log->debug($self->to_log({ name  => $op_name,
                                   tx_id => $tx_id,
                                   url   => $url }));
    ($data, $rc) = $self->send_http_request($c, $tx_id, $url, 'GET');
    if ($rc == 0 && $data && ref $data eq 'HASH' && $data->{success}) {
        $token = $data->{data}->{token};
    } else {
        $rc = 1;
    }
    $c->log->debug($self->to_log({ name   => $op_name,
                                   status => $rc ? 'failed' : 'success',
                                   tx_id  => $tx_id,
                                   url    => $url,
                                   data   => $self->data_to_str($data) }));
    return if $rc;
    # -------------------------------------------------------------------------

    if ($action eq 'register_content') {
        # check server --------------------------------------------------------
        my $server_id;
        $op_name = 'ALE check server';
        $err = '';
        $url = "$$cfg{proto}://$$cfg{host}/$$cfg{path}/servers";
        $c->log->debug($self->to_log({ name  => $op_name,
                                       tx_id => $tx_id,
                                       url   => $url }));
        ($data, $rc) = $self->send_http_request($c, $tx_id, $url, 'GET', $token);
        if ($rc == 0 && $data && ref $data eq 'HASH' && $data->{success}) {
            my ($server) = grep {$_->{server_url} eq $param_uri} @{$data->{data}->{server_list}};
            if ($server) {
                $server_id = $server->{server_id};
            }
        }
        $c->log->debug($self->to_log({ name   => $op_name,
                                       status => $rc ? 'failed' : 'success',
                                       tx_id  => $tx_id,
                                       url    => $url,
                                       msg    => $err // '',
                                       data   => $self->data_to_str($data) }));
        return if $rc;

        # server does not exist, create it ------------------------------------
        unless ($server_id) {
            $op_name = 'ALE create server';
            $err = '';
            my $body_ct = 'application/json';
            my $body    = encode_json({
                    server_name => $param_servername,
                    server_url  => $param_uri,
            });
            $c->log->debug($self->to_log({ name  => $op_name,
                                           tx_id => $tx_id,
                                           url   => $url,
                                           data  => $self->data_to_str($body) }));
            ($data, $rc) = $self->send_http_request($c, $tx_id, $url, 'POST', $token, $body_ct, $body);
            if ($rc == 0 && $data && ref $data eq 'HASH' && $data->{success}) {
                $server_id = $data->{data}->{server_id};
            } else {
                $rc = 1;
            }
            $c->log->debug($self->to_log({ name   => $op_name,
                                           status => $rc ? 'failed' : 'success',
                                           tx_id  => $tx_id,
                                           url    => $url,
                                           msg    => $err // '',
                                           data   => $self->data_to_str($data) }));
            return if $rc;
        }

        # fetch profile -------------------------------------------------------
        my $profile_id;
        $op_name = 'ALE check profile';
        $err = '';
        $url = "$$cfg{proto}://$$cfg{host}/$$cfg{path}/profiles";
        $c->log->debug($self->to_log({ name  => $op_name,
                                       tx_id => $tx_id,
                                       url   => $url }));
        ($data, $rc) = $self->send_http_request($c, $tx_id, $url, 'GET', $token);
        if ($rc == 0 && $data && ref $data eq 'HASH' && $data->{success}) {
            my ($profile) = grep {$_->{server_id} == $server_id} @{$data->{data}->{profile_list}};
            if ($profile) {
                $profile_id = $profile->{profile_id};
            }
        }
        $c->log->debug($self->to_log({ name   => $op_name,
                                       status => $rc ? 'failed' : 'success',
                                       tx_id  => $tx_id,
                                       url    => $url,
                                       msg    => $err // '',
                                       data   => $self->data_to_str($data) }));
        return if $rc;

        unless ($profile_id) {
            # profile does not exist, create it -------------------------------
            $op_name = 'ALE create profile';
            $err = '';
            my $body_ct = 'application/json';
            my $body    = encode_json({
                    profile_name => $param_servername,
                    server_id  => $server_id,
            });
            $c->log->debug($self->to_log({ name  => $op_name,
                                           tx_id => $tx_id,
                                           url   => $url,
                                           data  => $self->data_to_str($body) }));
            ($data, $rc) = $self->send_http_request($c, $tx_id, $url, 'POST', $token, $body_ct, $body);
            if ($rc == 0 && $data && ref $data eq 'HASH' && $data->{success}) {
                $profile_id = $data->{data}->{profile_id};
            } else {
                $rc = 1;
            }
            $c->log->debug($self->to_log({ name   => $op_name,
                                           status => $rc ? 'failed' : 'success',
                                           tx_id  => $tx_id,
                                           url    => $url,
                                           msg    => $err // '',
                                           data   => $self->data_to_str($data) }));
            return if $rc;
        }

        # update profile ------------------------------------------------------
        $op_name = 'ALE prepare profile update (register)';
        $err = '';
        $c->log->debug($self->to_log({ name  => $op_name,
                                       tx_id => $tx_id,
                                       url   => $url }));
        $ret = {
            method =>'POST',
            url => "$$cfg{proto}://$$cfg{host}/$$cfg{path}/devices",
            body => { macs => [{mac =>$new_mac}], profile_id => $profile_id},
            token => $token,
        };
    } elsif ($action eq 'unregister_content') {
        # we've to fetch the id first before constructing the delete request --
        my $device_id;
        $op_name = 'ALE prepare profile delete (unregister)';
        $err = '';
        $url = "$$cfg{proto}://$$cfg{host}/$$cfg{path}/devices";
        $c->log->debug($self->to_log({ name  => $op_name,
                                       tx_id => $tx_id,
                                       url   => $url }));
        ($data, $rc) = $self->send_http_request($c, $tx_id, $url, 'GET', $token);
        if ($rc == 0 && $data && ref $data eq 'HASH' && $data->{success}) {
            my ($device) = grep {uc $_->{mac} eq uc $old_mac} @{$data->{data}->{device_list}};
            if ($device) {
                $device_id = $device->{device_id};
            }
        }
        unless ($device_id) {
            $err = 'missing device_id';
            $rc = 1;
        }
        $c->log->debug($self->to_log({ name   => $op_name,
                                       status => $rc ? 'failed' : 'success',
                                       tx_id  => $tx_id,
                                       url    => $url,
                                       msg    => $err // '',
                                       data   => $self->data_to_str($data) }));
        return if $rc;

        $ret = {
            method =>'DELETE',
            url => "$$cfg{proto}://$$cfg{host}/$$cfg{path}/devices/$device_id",
            body => undef,
            token => $token,
        };
    }

    unless ($ret) {
        $c->log->error($self->to_log({ name   => 'ALE prepare request',
                                       status => 'failed',
                                       tx_id  => $tx_id,
                                       msg    => 'no prepared register/unregister request' }));
    }

    return $ret;
}

sub send_http_request {
    my ($self, $c, $tx_id, $url, $method, $token, $body_ct, $body) = @_;

    my ($res, $data, $rc);
    my $req = HTTP::Request->new($method => $url);

    unless ($token) {
        $req->header(':api_user_name' => $self->params->{credentials}->{user});
        $req->header(':api_password' => $self->params->{credentials}->{password});
    } else {
        $req->header('token' => $token);
    }

    $req->header('accept' => 'application/json');

    if ($method eq 'POST') {
        $req->content_type($body_ct) if $body_ct;
        $req->content($body) if $body;
    }

    $res = $self->_ua->request($req);
    if ($res->is_success) {
        if ($res->decoded_content) {
            try {
                $data = decode_json($res->decoded_content);
            } catch($e) {
                $c->log->error($self->to_log({ name   => 'Failed to parse JSON content',
                                               status => 'failed',
                                               tx_id  => $tx_id,
                                               url    => $url,
                                               msg    => $e,
                                               data   => $self->data_to_str($res->decoded_content) }));
                return ($data, 1);
            };
        }
    } else {
        $c->log->error($self->to_log({ name   => "$method reqeuest",
                                       status => 'failed',
                                       tx_id  => $tx_id,
                                       url    => $url,
                                       msg    => $res->status_line,
                                       data   => $self->data_to_str($res->decoded_content) }));
        return ($data, 1);
    }

    return ($data, 0);
}

around 'process_bootstrap_uri' => sub {
    my($orig_method, $self, $uri) = @_;
    $uri = $self->$orig_method($uri);
    $self->content_params->{uri} = $uri;
    $self->bootstrap_uri_server_name($uri);
    return $self->content_params->{uri};
};

sub bootstrap_uri_server_name{
    my($self,$uri) = @_;
    $uri ||= $self->content_params->{uri};
    #http://stackoverflow.com/questions/4826403/hash-algorithm-with-alphanumeric-output-of-20-characters-max
    $self->content_params->{server_name} ||= substr(md5_hex($uri),0,20);
    return $self->content_params->{server_name};
}

1;
