package Net::Async::Spotify::API::Base;

use strict;
use warnings;

our $VERSION = '0.001'; # VERSION
our $AUTHORITY = 'cpan:VNEALV'; # AUTHORITY

use Future::AsyncAwait;
use Log::Any qw($log);
use Syntax::Keyword::Try;

use Scalar::Util qw(weaken isweak);
use URI;
use URI::QueryParam;
use JSON::MaybeUTF8 qw(:v1);

use Net::Async::Spotify::Object;

=head1 NAME

Net::Async::Spotify::API::Base - Base Package for Spotify API Classes.

=head1 DESCRIPTION

Base class to be used by all Autogenerated modules of Spotify API

=head1 METHODS

=cut

sub new {
    my $self = bless { @_[1..$#_] }, $_[0];
    die "Net::Async::Spotify has to be provided" unless $self->spotify and $self->spotify->isa('Net::Async::Spotify');
    weaken($self->{spotify}) unless isweak($self->spotify);
    $self;
}

sub spotify { shift->{spotify} }


=head2 call_api

Calls Spotify API with the passed params.
Allowing also to accept user passed response type C<obj_type> as member of C<%args>

=cut

async sub call_api {
    my ($self, $request, $response_objs, %args) = @_;
    $log->tracef('CALL API CALLED: %s | %s | %s', $request, $response_objs, \%args);

    my ( $result, $request_headers, $request_uri, $request_content, $res_hash, $decoded_res, $mapped_res );
    # Prepare needed response params.
    # override if response type passed explicitly
    $response_objs = [$args{obj_type}] if exists $args{obj_type};
    $res_hash = {response_objs => $response_objs, uri => $request->{uri}};
    # Path Parameter
    if (my $path_parameter = $request->{param}{path_parameter} ) {
        # Path parameters should be always required.
        for my $key (keys $path_parameter->%*) {
            unless ( exists $args{$key} ) {
                $log->errorf('Required Path Parameter missing %s', $key);
                return;
            }
            $request->{uri} = $request->{uri} =~ s/{$key}/$args{$key}/r;
        }
    }
    $request_uri = URI->new($request->{uri});

    # Headers; at the moment only Authorization
    # Content header will be set only of content passed.
    my $authorize = $request->{param}{header}{Authorization};
    if (  $authorize and $authorize->{required} eq 'required' ) {
        $request_headers->{Authorization} = $self->spotify->token->header_string;
    }
    # Unfortunately the API documentation is not consistent so check all possibilities.
    if ( exists $request->{param}{header}{'Content-Type'}
            or exists $request->{param}{json_body_parameter}
            or $request->{method} eq 'POST'
    ) {
        $request_content = {
            content => {},
            content_type => 'application/json',
        };
    }

    # Query parameters
    for my $qp (keys $request->{param}{query_parameter}->%*) {
        $request_uri->query_param( $qp => $args{$qp} ) if exists $args{$qp};
        # There is no need to confirm data Type
        if ( !defined $request_uri->query_param($qp)
                and $request->{param}{query_parameter}{$qp}{required} eq 'required') {
            $log->errorf('Missing Required Query Parameter: %s', $qp);
            return;
        }
    }

    # Body parameters
    for my $bp (keys $request->{param}{json_body_parameter}->%*) {
        if ( exists $args{$bp} ) {
            unless ( $request->{param}{json_body_parameter}{$bp}{type} =~ /^array/
                or ref($args{$bp}) eq 'ARRAY' ) {
                $request_content->{content}{$bp} = $args{$bp};
            } else {
                push $request_content->{content}{$bp}->@*, split ',', $args{$bp};
            }
        } elsif ( $request->{param}{json_body_parameter}{$bp}{required} eq 'required' ) {
            $log->errorf('Missing Required Body Parameter: %s', $bp);
            return;
        }
    }

    $request_content->{content} = encode_json_utf8($request_content->{content}) if exists $request_content->{content};
    try {
        $result = await $self->spotify->http->do_request(
            method => $request->{method},
            uri => $request_uri,
            defined $request_content ? ($request_content->%*) : (),
            headers => $request_headers,
        );
    } catch ($e) {
        use Data::Dumper;
        $log->errorf('Error calling Spotify API request: %s | Error: %s', $request_uri->as_string, Dumper($e));
        return;
    }

    try {
        $decoded_res = $result->content ? decode_json_utf8($result->content) : $result->content;
    } catch ($e) {
        $log->warnf('Could not JSON decode Spotify API Response | Error: %s', $e);
        return { status_line => $result->status_line, content => $result->content };
    }
    # Separate Try/Catch for better error isolation
    try {
        $mapped_res = $decoded_res ? Net::Async::Spotify::Object->new($decoded_res, $res_hash) : $decoded_res;
    } catch ($e) {
        $log->warnf('Could not Map Spotify API Response to its Object %s | Error: %s | data: %s', $res_hash, $e, $decoded_res);
        return { status_line => $result->status_line, content => $decoded_res };
    }

    return { status_line => $result->status_line, content => $mapped_res };
}

1;
