### ----------------------------------------------------
### -- WWW::GCloud
### -- Licenses: Artistic-2.0
### -- Authors: Vadim Belman
### -- File: t/050-type-map.rakutest
### ----------------------------------------------------
use v6.e.PREVIEW;
use Test::Async;

use WWW::GCloud::R::Api;
use WWW::GCloud::R::Operation;
use WWW::GCloud::R::Option;
use WWW::GCloud::R::Status;
use WWW::GCloud::Record;
use WWW::GCloud;

plan 4;

class MyStatus is WWW::GCloud::R::Status {
    has Str $.helper = "not for json";
}
class MyOption is json-wrap(WWW::GCloud::R::Option) { }

subtest "Basic", {
    plan 4;

    my $gcloud = WWW::GCloud.new;

    my $json = q:to/JSON/;
    {
        "name": "test-op",
        "done": false,
        "error": {
            "code": "42",
            "message": "this is status"
        }
    }
    JSON

    my $op = $gcloud.gc-ctx: {
        WWW::GCloud::R::Operation.from-json($json);
    };

    isa-ok $op.error, WWW::GCloud::R::Status, "no map, the original type is used";

    my $unwrapped-op-json = $op.to-json(:sorted-keys);

    $gcloud.config.map-type: (WWW::GCloud::R::Status) => MyStatus;

    $op = $gcloud.gc-ctx: {
        WWW::GCloud::R::Operation.from-json($json);
    };

    isa-ok $op.error, MyStatus, "use class is used when map is isntalled";

    # The recommended way
    class MyOp is json-wrap(WWW::GCloud::R::Operation) {
        has Bool:D $.a-flag = False;
    }

    $gcloud.config.map-type: MyOp;

    $op = $gcloud.gc-ctx: {
        WWW::GCloud::R::Operation.from-json($json);
    };

    isa-ok $op, MyOp, "json-wrap trait simplifies life";
    is $op.to-json(:sorted-keys), $unwrapped-op-json, "user class attribute doesn't alter JSON serialization";
}

subtest "Array", {
    plan 4;

    my $gcloud = WWW::GCloud.new;

    my $json = q:to/JSON/;
    {
        "name": "Foo",
        "options": [
            {
                "name": "option1",
                "value": 42
            },
            {
                "name": "option2",
                "value": false
            }
        ]
    }
    JSON

    my class MyApi is json-wrap(WWW::GCloud::R::Api) { }

    $gcloud.config.map-types: MyApi, MyOption;

    my $api = $gcloud.gc-ctx: {
        WWW::GCloud::R::Api.from-json($json)
    };

    isa-ok $api, MyApi, "owner class is mapped";
    cmp-ok $api.options.all, "~~", MyOption, "positional attribute elemenets are mapped";

    is $api.options[1].name, "option2", "secont element 'name' deserialized correctly";
    is $api.options[1].value, False, "secont element 'value' deserialized correctly";
}

subtest "Hash", {
    plan 2;

    my class MyRec is gc-record {
        has WWW::GCloud::R::Status:D %.op-states;
    }

    my $gcloud = WWW::GCloud.new;

    $gcloud.config.map-type: (WWW::GCloud::R::Status) => MyStatus;

    my $json = q:to/JSON/;
    {
        "op-states": {
            "foo": {
                "code": 13,
                "message": "foo is wrong"
            },
            "bar": {
                "code": -1,
                "message": "bar is no better"
            }
        }
    }
    JSON

    my $rec = $gcloud.gc-ctx: {
        MyRec.from-json($json)
    };

    cmp-ok $rec.op-states.values.all, "~~", MyStatus, "all values deserialized into wrapper class";
    cmp-deeply
        $rec.op-states.values.map(*.message).sort.list,
        ("bar is no better", "foo is wrong"),
        "typed hash deserialized correctly";
}

subtest "Complex", {
    plan 2;

    my class MyRec is gc-record {
        has Array[WWW::GCloud::R::Status:D] %.op-states;
        has Hash[WWW::GCloud::R::Option:D,Int()] @.opt-idx;
    }

    my $gcloud = WWW::GCloud.new;

    $gcloud.config.map-type: (WWW::GCloud::R::Status) => MyStatus;
    $gcloud.config.map-type: MyOption;

    my $json = q:to/JSON/;
    {
        "op-states": {
            "foo": [
                {
                    "code": 13,
                    "message": "foo 1"
                },
                {
                    "code": 14,
                    "message": "foo 2"
                },
                {
                    "code": 42,
                    "message": "foo 3"
                }
            ],
            "bar": [
                {
                    "code": -13,
                    "message": "bar 1"
                },
                {
                    "code": -14,
                    "message": "bar 2"
                }
            ]
        },
        "opt-idx": [
            {
                "1": {
                    "name": "option1",
                    "value": 101
                },
                "2": {
                    "name": "option2",
                    "value": 102
                }
            },
            {
                "11": {
                    "name": "option3",
                    "value": 211
                },
                "12": {
                    "name": "option4",
                    "value": 2.12
                },
                "13": {
                    "name": "option5",
                    "value": false
                }
            }
        ]
    }
    JSON

    my $rec = $gcloud.gc-ctx: {
        MyRec.from-json($json)
    };

    subtest "Hash Attribute", {
        plan 3;

    CATCH {
        default {
            note "COMPLEX EX: {.^name}\n", .backtrace.full.Str;
            .rethrow
        }
    }
        is $rec.op-states.values.map(|*).elems, 5, "all values found in the hash";
        cmp-ok $rec.op-states.values.map(|*).all, "~~", MyStatus, "all values are of the expected type";
        cmp-deeply
            $rec.op-states.sort.map(|*.value).map(*.code).list,
            (-13, -14, 13, 14, 42),
            "object are seemingly deserialized correctly";
    }
    subtest "Array Attribute", {
        plan 3;
        is $rec.opt-idx.map(|*.values).elems, 5, "all objects found in the array";
        cmp-ok $rec.opt-idx.map(|*.values).all, "~~", MyOption, "all objects are of the expected type";
        cmp-deeply
            $rec.opt-idx.map(|*.sort.map(*.value)).map(*.value).list,
            (101, 102, 211, 2.12, False),
            "object are seemingly deserialized correctly";
    }
}


