### ----------------------------------------------------
### -- App::Tasks
### -- Licenses: Artistic-2.0
### -- Authors: Joelle Maslak
### -- File: t/28-trello.rakutest
### ----------------------------------------------------
use v6.d;
use Test;

#
# Copyright (C) 2023 Joelle Maslak
# All Rights Reserved - See License
#

use App::Tasks;
use App::Tasks::Config;
use App::Tasks::Trello;
use Cro::HTTP::Server;
use Cro::HTTP::Router;
use File::Temp;

my $port = setup-listener();
my $url = "http://localhost:$port/";

my $trello = App::Tasks::Trello.new(
    api-key  => "mykey",
    token    => "mytoken",
    base-url => $url,
);


subtest 'get_boards', {
    my $boards = $trello.get_boards();
    is $boards.elems, 2, "Proper number of elements (1)";
    is $boards[1]<id>, "5b689b3228998cf3f01c629e", "Proper ID (1)";
    is $boards[1]<name>, "Never ending Backlog", "Proper name (1)";
    nok $boards[1]<url>:exists, "URL not defined (1)";

    $boards = $trello.get_boards(fields => ("url", "name"));
    is $boards.elems, 2, "Proper number of elements (2)";
    is $boards[1]<id>, "5b689b3228998cf3f01c629e", "Proper ID (2)";
    is $boards[1]<name>, "Never ending Backlog", "Proper name (2)";
    is $boards[1]<url>, "https://trello.com/b/pLu77kV7/neverending-backlog",
        "proper URL (2)";

    done-testing;
}

subtest 'get_board_id_by_name', {
    my $id = $trello.get_board_id_by_name('Never ending Backlog');
    is $id, "5b689b3228998cf3f01c629e", "Proper ID";
    dies-ok {$trello.get_board_id_by_name('foo')}, "Invalid board name dies";
}

subtest 'get_lists', {
    my $boards = $trello.get_lists(board => "5b689b3228998cf3f01c629e");
    is $boards.elems, 1, "Proper number of elements (1)";
    is $boards[0]<id>, "abc123list", "Proper ID (1)";
    is $boards[0]<name>, "Today", "Proper name (1)";
    nok $boards[0]<pos>:exists, "POS not defined (1)";

    $boards = $trello.get_lists(board => "5b689b3228998cf3f01c629e", fields => ("pos", "name"));
    is $boards.elems, 1, "Proper number of elements (2)";
    is $boards[0]<id>, "abc123list", "Proper ID (2)";
    is $boards[0]<name>, "Today", "Proper name (2)";
    is $boards[0]<pos>, 8192, "Proper POS (2)";

    done-testing;
}

subtest 'get_cards', {
    my $boards = $trello.get_cards(board => "5b689b3228998cf3f01c629e");
    is $boards.elems, 1, "Proper number of elements (1)";
    is $boards[0]<id>, "abc123", "Proper ID (1)";
    is $boards[0]<name>, "Task", "Proper name (1)";
    nok $boards[0]<pos>:exists, "POS not defined (1)";

    $boards = $trello.get_cards(board => "5b689b3228998cf3f01c629e", fields => ("pos", "name"));
    is $boards.elems, 1, "Proper number of elements (2)";
    is $boards[0]<id>, "abc123", "Proper ID (2)";
    is $boards[0]<name>, "Task", "Proper name (2)";
    is $boards[0]<pos>, 8192, "Proper POS (2)";

    done-testing;
}

subtest 'sync', {
    my $tmpdir = tempdir.IO;    # Get IO::Path object for tmpdir.
    my $cwd = $*CWD;
    say "CWD: {$*CWD.Str}";
    
    my ($fn, $fh) = tempfile;
    $fh.say: "theme: 'no-color'";
    $fh.say: "trello:";
    $fh.say: "  api-key: \"mykey\"";
    $fh.say: "  token: \"mytoken\"";
    $fh.say: "  base-url: \"{$url}\"";
    $fh.say: "  tasks:";
    $fh.say: "    \"Never ending Backlog\":";
    $fh.say: "      Today: \"tag1\"";
    $fh.close;

    my ($fn2, $fh2) = tempfile;
    $fh2.close;
    my $conf = App::Tasks::Config.read-config($fn.IO, $fn2.IO);
    my $task = App::Tasks.new( :data-dir($tmpdir), :config($conf) );

    my @tasks = $task.read-tasks;
    is @tasks.elems, 0, "Proper number of task elements exist (start)";

    lives-ok { $task.task-trello-sync }, "Sync doesn't die";

    @tasks = $task.read-tasks;
    is @tasks.elems, 1, "Proper number of task elements exist (A)";
    $task.task-trello-sync;
    is @tasks.elems, 1, "Proper number of task elements exist (B)";
    is @tasks[0].title, "Task", "Proper task title";
    is @tasks[0].trello-id, "abc123", "Proper Trello ID";

    done-testing;
}

done-testing;

# Basically a copy of Cro::TCP::Listener, but doesn't bother with
# allowing nodelay to be set and grabs the port number with a nest of
# tap and supplier.
class NuListener does Cro::Source {
    has Str $.host = 'localhost';
    has Cro::Port $.port is required;

    method produces() { Cro::TCP::ServerConnection }

    method incoming() {
        my $listen-socket = IO::Socket::Async.listen($!host, $!port);
        my $supplier = Supplier::Preserving.new();
        my $tap = $listen-socket.tap( { $supplier.emit($_) } );
        $!port = $tap.socket-port.result;

        supply {
            whenever $supplier.Supply() -> $socket {
                emit Cro::TCP::ServerConnection.new(:$socket);
            }
        }
    }
}

# Sample services
sub setup-listener() {
    my $application = route {
        get -> '1', 'members', 'me', 'boards', :$key!, :$token!, :$fields {
            if $key ne 'mykey' or $token ne 'mytoken' {
                forbidden;
            } else {
                my @data =
                    ( 
                        {
                             name => "Greatest Product Roadmap",
                             id   => "5b6893f01cb3228998cf629e",
                             url  => "https://trello.com/b/Fqd6NosI/greatest-product-roadmap",
                         },
                         {
                             name => "Never ending Backlog",
                             id   => "5b689b3228998cf3f01c629e",
                             url  => "https://trello.com/b/pLu77kV7/neverending-backlog",
                         },
                     );
                my @fields = $fields.split(",");
                @fields.append("id");
                for @data<> -> $elem {
                    for $elem.keys -> $key {
                        $elem{$key}:delete unless $key ∈ @fields;
                    }
                }
                content 'application/json', @data;
            }
        }
        get -> '1', 'boards', $board, 'lists', :$key!, :$token!, :$fields {
            if $key ne 'mykey' or $token ne 'mytoken' or $board ne '5b689b3228998cf3f01c629e' {
                forbidden;
            } else {
                my @data =
                    (
                        {
                            closed => False,
                            id =>"abc123list",
                            idBoard => "abc123",
                            name => "Today",
                            pos => 8192,
                            softLimit => Any,
                            subscribed => False
                        },
                    );
                my @fields = $fields.split(",");
                @fields.append("id");
                for @data<> -> $elem {
                    for $elem.keys -> $key {
                        $elem{$key}:delete unless $key ∈ @fields;
                    }
                }
                content 'application/json', @data;
            }
        }
        get -> '1', 'boards', $board, 'cards', :$key!, :$token!, :$fields {
            if $key ne 'mykey' or $token ne 'mytoken' or $board ne '5b689b3228998cf3f01c629e' {
                forbidden;
            } else {
                my @data =
                    (
                        {
                            id =>"abc123",
                            name => "Task",
                            pos => 8192,
                            idList => 'abc123list',
                        },
                    );
                my @fields = $fields.split(",");
                @fields.append("id");
                for @data<> -> $elem {
                    for $elem.keys -> $key {
                        $elem{$key}:delete unless $key ∈ @fields;
                    }
                }
                content 'application/json', @data;
            }
        }
    }

    my Cro::Service $service = Cro::HTTP::Server.new(
        :host('localhost'), :port(0), :$application
    );

    # Swap out the TCP component...
    $service.components[0] = NuListener.new(:host('localhost'), :port(0));

    $service.start;
    my $ephemeral-port = $service.components[0].port;

    return $ephemeral-port;
}


