package NGCP::Panel::Utils::DeviceBootstrap::Snom;

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 Digest::SHA qw(hmac_sha256_base64);
use URI;
use TryCatch;
use Data::Dumper;
use Data::UUID;

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

sub rpc_server_params {
    my $self = shift;
    my $cfg  = {
        proto    => 'https',
        host     => 'secure-provisioning.snom.com',
        path     => 'api/v1',
    };
    $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_uri = $self->content_params->{uri};
    my $credentials = {
        id => $self->params->{credentials}->{user},
        key => $self->params->{credentials}->{password}
    };
    my $company_url;

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

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

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

    # first, get company url --------------------------------------------------
    $op_name = 'Snom get tokens';
    $err = '';
    $url = "$$cfg{proto}://$$cfg{host}/$$cfg{path}/tokens/".$credentials->{id};
    $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', $credentials);
    if ($rc == 0 && $data && ref $data eq 'HASH' && $data->{links}->{company}) {
        $company_url = $data->{links}->{company};
        $c->log->debug($self->to_log({ name   => $op_name,
                                       status => 'success',
                                       tx_id  => $tx_id,
                                       url    => $url,
                                       data   => $self->data_to_str($data) }));
        $url = $data->{links}->{company};
    } else {
        $rc = 1;
    }
    $c->log->debug($self->to_log({ name   => 'Snom get tokens',
                                   status => $rc ? 'failed' : 'success',
                                   tx_id  => $tx_id,
                                   url    => $url,
                                   data   => $self->data_to_str($data) }));
    return if $rc;
    #--------------------------------------------------------------------------

    if ($action eq 'register_content') {

        # get product groups --------------------------------------------------
        my ($product_group_id, $setting_id);
        $op_name = 'Snom get product groups';
        $err = '';
        $url = "$$cfg{proto}://$$cfg{host}/$$cfg{path}/product-groups/";
        $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', $credentials);
        if ($rc == 0 && $data && ref $data eq 'ARRAY') {
            my ($product_group) = grep {$_->{name} eq $self->params->{redirect_params}->{product_family}} @$data;
            if ($product_group) {
                $product_group_id = $product_group->{uuid};
            }
            else {
                $err = 'specified product family not found';
                $rc = 1;
            }
        } 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;

        # get settings ---------------------------------------------------------
        $op_name = 'Snom get settings';
        $err = '';
        $url = "$$cfg{proto}://$$cfg{host}/$$cfg{path}/settings/";
        $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', $credentials);
        if ($rc == 0 && $data && ref $data eq 'ARRAY') {
            foreach my $setting (@$data) {
                if ( ($setting->{param_name} eq 'setting_server') &&
                     ($setting->{section}    eq 'phone-settings') )
                {
                    $setting_id = $setting->{uuid};
                    last;
                }
            }
            unless ($setting_id) {
                $err = 'setting for redirection server not found.';
                $rc = 1;
            }
        } 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 -------------------------------------------------------
        $op_name = 'Snom check profiles';
        $err = '';
        $url = "$company_url/provisioning-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', $credentials);
        if ($rc == 0 && $data && ref $data eq 'ARRAY') {
            # ok, noop
        } 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;

        # process profile -----------------------------------------------------
        my $profile_id;
        my ($profile) = grep {$_->{name} eq $self->params->{redirect_params}->{profile}} @$data;
        if ($profile) {
            $profile_id = $profile->{uuid};
        } elsif (length $self->params->{redirect_params}->{profile}) {
            #profile does not exist, create it
            $op_name = 'Snom create profile';
            $err = '';
            my $body = encode_json({
                name => $self->params->{redirect_params}->{profile},
                product_group  => $product_group_id,
                autoprovisioning_enabled => 'true',
            });
            my $body_ct = 'application/json';
            $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', $credentials, $body_ct, $body);
            if ($rc == 0 && $data && ref $data eq 'HASH' && $data->{uuid}) {
                $profile_id = $data->{uuid};
            } else {
                $err = 'could not fetch profile uuid';
                $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 = 'Snom prepare profile update (register)';
        $err = '';
        my $body = {
            mac => $new_mac,
            autoprovisioning_enabled => 'true',
            settings_manager => {
                $setting_id => {
                    value => $param_uri,
                    attrs => {
                        perm => 'RW'
                    }
                }
            }
        };

        $body->{provisioning_profile} = $profile_id if ($profile_id);
        $url = "$company_url/endpoints/$new_mac";
        $c->log->debug($self->to_log({ name  => $op_name,
                                       tx_id => $tx_id,
                                       url   => $url }));
        $ret = {
            method =>'PUT',
            url => $url,
            body => $body,
            hawk => $self->generate_header($url, 'PUT',
            {
                credentials => $credentials,
                content_type => '',
                payload => '',
            }),
        };
    } elsif ($action eq 'unregister_content') {
        # we've to fetch the id first before constructing the delete request --
        $op_name = 'Snom prepare profile delete (unregister)';
        $err = '';
        $url = "$company_url/endpoints/";
        $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', $credentials);
        if ($rc == 0 && $data && ref $data eq 'ARRAY') {
            my $device_id;
            my ($device) = grep {uc($_->{mac}) eq uc($old_mac)} @$data;
            if ($device) {
                $device_id = $device->{mac};
            }
            $url = "$company_url/endpoints/$device_id";
            $ret = {
                method =>'DELETE',
                url => $url,
                body => undef,
                hawk => $self->generate_header($url, 'DELETE',
                {
                    credentials => $credentials,
                    content_type => '',
                    payload => ''
                }),
            };
        } 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;
    }

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

    return $ret;
}

sub generate_header {
    my ($self, $uri, $method, $options) = @_;

    my $time = time;
    my $credentials = $options->{credentials};

    my @chars = ("A".."Z", "a".."z");
    my $nonce;
    $nonce .= $chars[rand @chars] for 1..8;

    $uri = URI->new($uri);

    my $hash = $self->calculate_payload_hash(
                    $options->{payload},
                    $options->{content_type},
                    $credentials->{key}
    );

    my $artifacts = {
        ts => $time,
        nonce => $nonce,
        method => $method,
        resource => $uri->path_query,
        host => $uri->host,
        port => $uri->port,
        hash => $hash || ''
    };

    my $mac = $self->calculate_mac($credentials, $artifacts);

    my $auth  = 'Hawk';
       $auth .= ' mac="' . $mac . '",';
       $auth .= ' hash="' . $artifacts->{hash} . '",' unless $hash eq '';
       $auth .= ' id="' . $credentials->{id} . '",';
       $auth .= ' ts="' . $artifacts->{ts} . '",';
       $auth .= ' nonce="' . $artifacts->{nonce} .'"';

    return $auth;

}

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

    my ($res, $data, $rc);
    my $req = HTTP::Request->new($method => $url);
    $req->header('Authorization' => $self->generate_header($url, $method,
                    {
                      credentials => $credentials,
                      content_type => '',
                      payload => ''
                    }
    ));
    $req->header('accept' => 'application/json');

    if ($method eq 'POST') {
        if ($body_ct) {
            $req->content_type($body_ct);
        }
        $req->content($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);
}

sub calculate_mac {
    my ($self, $credentials, $options) = @_;

    my $normalized = $self->generate_normalized_string($options);

    my $result_b64 = "";
    $result_b64 = hmac_sha256_base64($normalized, $credentials->{key});
    while (length($result_b64) % 4) {
        $result_b64 .= '=';
    }

    return $result_b64;
}

sub calculate_payload_hash {
    my ($self, $payload, $content_type, $key) = @_;

    return '' if $payload eq '';

    my $pload  = "hawk.1.payload\n";
       $pload .= $content_type . "\n";
       $pload .= ($payload || '');

    my $result_b64 = hmac_sha256_base64($pload, $key);

    while (length($result_b64) % 4) {
        $result_b64 .= '=';
    }

    return $result_b64;
}

sub generate_normalized_string {
    my ($self, $options) = @_;

    my $normalized = "hawk.1.header\n";
       $normalized .= $options->{ts}."\n";
       $normalized .= $options->{nonce}."\n";
       $normalized .= uc($options->{method}) . "\n";
       $normalized .= $options->{resource}."\n";
       $normalized .= $options->{host}."\n";
       $normalized .= $options->{port}."\n";
       $normalized .= "\n";
       $normalized .= "\n"; # this is also needed for a healthy header ( and mac ) since an extension is allowed in hawk

    return $normalized;
}

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

1;
