#!/usr/bin/env python3

import asyncio, os, sys, tempfile
from datetime import datetime
import time
from tabulate import tabulate
from colorama import Fore, Style

import api, lib.util

known_attrs = ["desc", "rank", "due", "project", "bounty"]

async def add_task(task_args, server_name, port):
    task = {
        "title": None,
        "tags": [],
        "desc": None,
        "assign": [],
        "project": [],
        "due": None,
        "rank": None,
        "bounty": None,
        "created_at": lib.util.now(),
        "state": "open"
    }
    # Everything that isn't an attribute is part of the title
    # Open text editor if desc isn't set to write desc text
    title_words = []
    for arg in task_args:
        if arg[0] == "+":
            tag = arg
            if tag in task["tags"]:
                print(f"error: duplicate tag {tag} in task", file=sys.stderr)
                sys.exit(-1)
            task["tags"].append(tag)
        elif arg[0] == "@":
            assign = arg
            if assign in task["assign"]:
                print(f"error: duplicate assign {assign} in task", file=sys.stderr)
                sys.exit(-1)
            task["assign"].append(assign)
        elif ":" in arg and arg.split(":")[0] in known_attrs:
            attr, val = arg.split(":", 1)
            set_task_attr(task, attr, val)
        else:
            title_words.append(arg)

    title = " ".join(title_words)
    if len(title) == 0:
        print("Error: Title is required")
        exit(-1)
    task["title"] = title
    if task["desc"] is None:
        task["desc"] = prompt_description_text(task)
    
    if task["desc"].strip() == '':
        print("Abort adding the task due to empty description.")
        exit(-1)
    
    if task["rank"] is not None:
        task["rank"] = round(task["rank"], 4)

    if task["bounty"] is not None:
        task["bounty"] = round(task["bounty"], 4)
    
    try:
        if task["ref_id"].strip() == '':
            task.pop('ref_id')
        if task["workspace"].strip() == '':
            task.pop('workspace')
    except KeyError:
        pass

    ref = await api.add_task(task, server_name, port)
    if ref:
        return ref, title
    else:
        print("You don't have write access")
        exit(-1)

def prompt_text(comment_lines):
    temp = tempfile.NamedTemporaryFile()
    temp.write(b"\n")
    for line in comment_lines:
        temp.write(line.encode() + b"\n")
    temp.flush()
    editor = os.environ.get('EDITOR') if os.environ.get('EDITOR') else 'nano'    
    os.system(f"{editor} {temp.name}")
    desc = open(temp.name, "r").read()
    # Remove comments and empty lines from desc
    cleaned = []
    for line in desc.split("\n"):
        if line == "# ------------------------ >8 ------------------------":
            break
        if line.startswith("#"):
            continue
        cleaned.append(line)

    return "\n".join(cleaned)

def prompt_description_text(task):
    return prompt_text([
        "# Write task description above this line.",
        "# These lines will be removed.",
        "# An empty description aborts adding the task",
        "\n# ------------------------ >8 ------------------------",
        "# Do not modify or remove the line above.",
        "# Everything below it will be ignored.",
        f"\n{tabulate_task(task, True)}"
    ])

def prompt_comment_text():
    return prompt_text([
        "# Write comments above this line",
        "# These lines will be removed"
    ])

def prompt_description_edit(text):
    return prompt_text([
        f"{text}"
        "# Edit the task description above this line",
        "# These lines will be removed"
    ])

def set_task_attr(task, attr, val):
    if attr not in known_attrs:
        print(f"Error: invalid attribute: {attr} {val}")
        print("Task is not added")
        exit(-1)

    if val.lower() == "none":
        task[attr] = None
    else:
        val = convert_attr_val(attr, val)
        task[attr] = val

    lib.util._enforce_task_format(task)

def convert_attr_val(attr, val):
    templ = lib.util.task_template

    if attr in ["desc", "title"]:
        assert templ[attr] == str
        return val
    elif attr == "rank":
        try:
            return float(val)
        except ValueError:
            print(f"error: rank value {val} isn't convertable to float",
                  file=sys.stderr)
            sys.exit(-1)
    elif attr == "bounty":
        try:
            return float(val)
        except ValueError:
            print(f"error: bounty value {val} isn't convertable to float",
                  file=sys.stderr)
            sys.exit(-1)
    elif attr == "due":
        # Other date formats not yet supported... ez to add
        if len(val) != 4:
            print(f"Error: due date must be of length 4 in mmyy format")
            sys.exit(-1)
        date = datetime.now().date()
        year = int(date.strftime("%Y"))%100
        try:
            dt = datetime.strptime(f"18:00 {val}{year}", "%H:%M %d%m%y")
            if dt.date() < date:
                dt = datetime.strptime(f"18:00 {val}{year+1}", "%H:%M %d%m%y")
        except ValueError:
            print(f"error: unknown date format {val}")
            sys.exit(-1)
        due = lib.util.datetime_to_unix(dt)
        return due
    elif attr == "project":
        try:
            return [val]
        except ValueError:
            print(f"error: project value {val} isn't convertable to list",
                  file=sys.stderr)
            sys.exit(-1)
    else:
        print(f"error: unhandled attr '{attr}' = {val}")
        sys.exit(-1)

async def show_active_tasks(workspace, server_name, port):
    refids = await api.get_ref_ids(server_name, port)
    tasks = []
    for refid in refids:
        tasks.append(await api.fetch_task(refid, server_name, port))
    list_tasks(tasks, workspace, [])

async def show_deactive_tasks(month_ts, workspace, server_name, port):
    tasks = await api.fetch_deactive_tasks(month_ts, server_name, port)
    list_tasks(tasks, workspace, [])

async def show_log(server_name, port, timeframe):
    # fetch all tasks
    refids = await api.get_ref_ids(server_name, port)
    tasks = await api.fetch_deactive_tasks(None, server_name, port)
    for refid in refids:
        tasks.append(await api.fetch_task(refid, server_name, port))

    # list tasks events within a timeframe
    if timeframe == "day":
        timestamp = 86400
    elif timeframe == "week" or timeframe == None:
        timestamp = 604800
    elif timeframe == "month":
        timestamp = 2628002
    else:
        print(f"Error: invalid timeframe {timeframe}")
        print("valid timeframes are: 'day', 'week' and 'month'")
        print("try: tau log week")
        exit(-1)
    res_events = []
    now = lib.util.now()
    for task in tasks:
        events = task["events"]
        for event in events:
            # if timestamp is in ms convert it to s
            event_ts = int(event["timestamp"])
            if event_ts > 10e10:
                event_ts //= 1000
            date = lib.util.unix_to_datetime(event_ts)
            if (now - event_ts) < timestamp:
                if event["action"] == "state":
                    action = "stopped" if event["content"] == "stop" else f"{event["content"]}ed"
                    res = f"{date}: {event["author"]} {action} task: ({task["ref_id"][:6]}) '{task["title"]}'"
                    res_events.append(res)
                if event["action"] == "comment":
                    res = f"{date}: {event["author"]} commented on task: ({task["ref_id"][:6]}) '{task["title"]}'"
                    res_events.append(res)
                if event["action"] in ["assign", "tags"] :
                    res = f"{date}: {event["author"]} added {event["content"][1:]} to {event["action"]} in task: ({task["ref_id"][:6]}) '{task["title"]}'"
                    res_events.append(res)
    for i in res_events:
        print(i)

def list_tasks(tasks, workspace, filters):
    print(f"Workspace: {workspace}")
    headers = ["ID", "Title", "Status", "Project",
               "Tags", "assign", "Rank", "Due", 
               "Bounty", "RefID"]
    table_rows = []
    for id, task in enumerate(tasks, 1):
        if task is None:
            continue
        if is_filtered(task, filters):
            continue
        ref_id = task["ref_id"][:6]
        title = task["title"]
        status = task["state"]
        # project = task["project"] if task["project"] is not None else ""
        tags = " ".join(f"+{tag}" for tag in task["tags"])
        assign = " ".join(f"@{assign}" for assign in task["assign"])
        project = " ".join(f"{project}" for project in task["project"])
        if task["due"] is None:
            due = ""
        else:
            dt = lib.util.unix_to_datetime(task["due"])
            due = dt.strftime("%H:%M %d/%m/%y")

        rank = round(task["rank"], 4) if task["rank"] is not None else ""

        bounty = "$" + str(round(task["bounty"], 4)) if task["bounty"] is not None else ""

        if status == "start":
            id =        Fore.GREEN + str(id)         + Style.RESET_ALL
            title =     Fore.GREEN + str(title)      + Style.RESET_ALL
            status =    Fore.GREEN + str(status)     + Style.RESET_ALL
            project =   Fore.GREEN + str(project)    + Style.RESET_ALL
            tags =      Fore.GREEN + str(tags)       + Style.RESET_ALL
            assign =    Fore.GREEN + str(assign)     + Style.RESET_ALL
            rank =      Fore.GREEN + str(rank)       + Style.RESET_ALL
            due =       Fore.GREEN + str(due)        + Style.RESET_ALL
            bounty =    Fore.GREEN + str(bounty)     + Style.RESET_ALL
            ref_id =    Fore.GREEN + str(ref_id)     + Style.RESET_ALL
        elif status == "pause":
            id =        Fore.YELLOW + str(id)        + Style.RESET_ALL
            title =     Fore.YELLOW + str(title)     + Style.RESET_ALL
            status =    Fore.YELLOW + str(status)    + Style.RESET_ALL
            project =   Fore.YELLOW + str(project)   + Style.RESET_ALL
            tags =      Fore.YELLOW + str(tags)      + Style.RESET_ALL
            assign =    Fore.YELLOW + str(assign)    + Style.RESET_ALL
            rank =      Fore.YELLOW + str(rank)      + Style.RESET_ALL
            due =       Fore.YELLOW + str(due)       + Style.RESET_ALL
            bounty =    Fore.YELLOW + str(bounty)    + Style.RESET_ALL
            ref_id =    Fore.YELLOW + str(ref_id)    + Style.RESET_ALL
        elif status == "stop":
            id =        Fore.RED + str(id)           + Style.RESET_ALL
            title =     Fore.RED + str(title)        + Style.RESET_ALL
            status =    Fore.RED + str(status)       + Style.RESET_ALL
            project =   Fore.RED + str(project)      + Style.RESET_ALL
            tags =      Fore.RED + str(tags)         + Style.RESET_ALL
            assign =    Fore.RED + str(assign)       + Style.RESET_ALL
            rank =      Fore.RED + str(rank)         + Style.RESET_ALL
            due =       Fore.RED + str(due)          + Style.RESET_ALL
            bounty =    Fore.RED + str(bounty)       + Style.RESET_ALL
            ref_id =    Fore.RED + str(ref_id)       + Style.RESET_ALL
        else:
            #id =       Style.DIM  + str(id)         + Style.RESET_ALL
            #title =    Style.DIM  + str(title)      + Style.RESET_ALL
            #status =   Style.DIM  + str(status)     + Style.RESET_ALL
            project =   Style.DIM  + str(project)    + Style.RESET_ALL
            tags =      Style.DIM  + str(tags)       + Style.RESET_ALL
            #assign =   Style.DIM  + str(assign)     + Style.RESET_ALL
            rank =      Style.DIM  + str(rank)       + Style.RESET_ALL
            due =       Style.DIM  + str(due)        + Style.RESET_ALL
            bounty =    Style.DIM  + str(bounty)     + Style.RESET_ALL
            #ref_id =   Style.DIM  + str(ref_id)     + Style.RESET_ALL

        rank_value = task["rank"] if task["rank"] is not None else 0
        row = [
            id,
            title,
            status,
            project,
            tags,
            assign,
            rank,
            due,
            bounty,
            ref_id
        ]
        table_rows.append((rank_value, row))

    table = [row for (_, row) in
             sorted(table_rows, key=lambda item: item[0], reverse=True)]
    print(tabulate(table, headers=headers))

async def show_task(refid, server_name, port):
    task = await api.fetch_task(refid, server_name, port)
    task_table(task)
    return 0

async def show_archive_task(ref_id, month_ts, server_name, port):
    task = await api.fetch_archive_task(ref_id, month_ts, server_name, port)
    task_table(task)
    return 0

def tabulate_task(task, prompt):
    tags = " ".join(f"+{tag}" for tag in task["tags"])
    assign = " ".join(f"{assign}" for assign in task["assign"])
    project = " ".join(f"{project}" for project in task["project"])
    rank = round(task["rank"], 4) if task["rank"] is not None else ""
    bounty = round(task["bounty"], 4) if task["bounty"] is not None else ""
    if task["due"] is None:
        due = ""
    else:
        dt = lib.util.unix_to_datetime(task["due"])
        due = dt.strftime("%H:%M %d/%m/%y")

    assert task["created_at"] is not None
    dt = lib.util.unix_to_datetime(task["created_at"])
    created_at = dt.strftime("%H:%M %d/%m/%y")

    if prompt:
        task["ref_id"] = ''
        task["workspace"] = ''

    table = [
        ["RefID:", task["ref_id"]],
        ["Title:", task["title"]],
        ["Workspace:", task["workspace"]],
        ["Description:", task["desc"]],
        ["Status:", task["state"]],
        ["Project:", project],
        ["Tags:", tags],
        ["Assign:", assign],
        ["Rank:", rank],
        ["Due:", due],
        ["Bounty:", bounty],
        ["Created:", created_at],
    ]
    return tabulate(table, headers=["Attribute", "Value"])

def task_table(task):
    print(tabulate_task(task, False))

    table = []
    for event in task["events"]:
        act, who, when, args = event["action"], event["author"], event["timestamp"], event["content"]
        when = lib.util.unix_to_datetime(when)
        when = when.strftime("%H:%M %d/%m/%y")
        
        if act == "due" and when is not None:
            due_date = lib.util.unix_to_datetime(args)
            due_date = due_date.strftime("%H:%M %d/%m/%y")
            table.append([
                Style.DIM + f"{who} changed {act} to {due_date}" + Style.RESET_ALL,
                "",
                Style.DIM + when + Style.RESET_ALL
            ])
        elif act == "tags" or act == "assign":
            val = f"{args}"
            event = f"{who} added {val} to {act}"
            if val[0] == "-":
                event = f"{who} removed {val[1:]} from {act}"
            table.append([
                Style.DIM + event + Style.RESET_ALL,
                "",
                Style.DIM + when + Style.RESET_ALL
            ])
        elif act == "state":
            status = args
            if status == "pause":
                status_verb = "paused"
            elif status in ["start", "open"]:
                status_verb = f"{status}ed"
            elif status == "stop":
                status_verb = f"stopped"
            else:
                print(f"internal error: unhandled task state {status}",
                      file=sys.stderr)
                sys.exit(-2)

            table.append([
                f"{who} {status_verb} task",
                "",
                Style.DIM + when + Style.RESET_ALL
            ])
        elif act == "comment":
            continue
        else:
            table.append([
                Style.DIM + f"{who} changed {act} to {args}" + Style.RESET_ALL,
                "",
                Style.DIM + when + Style.RESET_ALL
            ])
    print(tabulate(table))

    table = []
    for event in task['events']:
        act, who, when, args = event["action"], event["author"], event["timestamp"], event["content"]
        when = lib.util.unix_to_datetime(when)
        when = when.strftime("%H:%M %d/%m/%y")
        if act == "comment":
            comment = args
            table.append([
                f"{who}>",
                wrap_comment(comment, 58),
                Style.DIM + when + Style.RESET_ALL
            ])
    if len(table) > 0:
        print("Comments:")
    print(tabulate(table))

def wrap_comment(comment, width):
    lines = []
    line_start = 0
    for i, char in enumerate(comment):
        if char == ' ' and (i - line_start >= width):
            lines.append(comment[line_start:i + 1])
            line_start = i + 1

    if line_start < len(comment):
        lines.append(comment[line_start:])
    return '\n'.join(lines)

async def modify_task(refid, args, server_name, port):
    task = await api.fetch_task(refid, server_name, port)
    current_assigns = task["assign"]
    current_tags = task["tags"]
    changes = {}
    changes["assign"] = []
    changes["tags"] = []
    for arg in args:
        # This must go before the next elif block
        if arg.startswith("@") or (arg.startswith("-@") and arg[2:] in current_assigns):
            changes["assign"].append(arg)
        elif arg.startswith("+") or (arg.startswith("-") and arg[1:] in current_tags):
            changes["tags"].append(arg)
        elif arg.lower() in ["desc", "description"]:
            desc = task["desc"]
            new_desc = prompt_description_edit(desc).lstrip()
            if desc == new_desc:
                print("Abort due to unchanged description")
                exit(-1)
            changes["desc"] = new_desc
        elif ":" in arg:
            attr, val = arg.split(":", 1)
            if val.lower() == "none":
                if attr not in ["project", "rank", "due", "bounty"]:
                    print(f"error: invalid you cannot set {attr} to none",
                          file=sys.stderr)
                    return -1
                val = None
            else:
                val = convert_attr_val(attr, val)
            changes[str(attr)] = val
        else:
            print(f"warning: unknown arg '{arg}'. Skipping...", file=sys.stderr)
    if not await api.modify_task(refid, changes, server_name, port):
        print("You don't have write access")
        exit(-1)
    return 0

async def change_task_status(refid, status, server_name, port):
    task = await api.fetch_task(refid, server_name, port)
    assert task is not None
    title = task["title"]

    if not await api.change_task_status(refid, status, server_name, port):
        return -1

    if status == "start":
        print(f"Started task '{title}'")
    elif status == "pause":
        print(f"Paused task '{title}'")
    elif status == "stop":
        print(f"Completed task '{title}'")
    elif status == "open":
        print(f"Opened task '{title}'")

    return 0

async def comment(refid, args, server_name, port):
    if not args:
        comment = prompt_comment_text()
    else:
        comment = " ".join(args)

    if comment.strip() == '':
        print("Abort adding comment due to empty content.")
        exit(-1)

    if not await api.add_task_comment(refid, comment, server_name, port):
        print("You don't have write access")
        exit(-1)

    # Two json rpcs back to back cause Unexpected EOF error
    time.sleep(0.1)
    task = await api.fetch_task(refid, server_name, port)
    assert task is not None
    title = task["title"]
    print(f"Commented on task '{title}'")
    return 0

def is_filtered(task, filters):
    for fltr in filters:
        if fltr.startswith("+"):
            tag = fltr[1:]
            if tag not in task["tags"]:
                return True
        elif fltr.startswith("@"):
            assign = fltr[1:]
            if assign not in task["assign"]:
                return True
        elif ":" in fltr:
            attr, val = fltr.split(":", 1)
            if val.lower() == "none":
                if attr not in ["project", "rank", "due", "bounty"]:
                    print(f"error: invalid you cannot set {attr} to none",
                            file=sys.stderr)
                    sys.exit(-1)
                if task[attr] is not None:
                    return True
            elif attr == "state" :
                if val not in ["open", "start", "pause"]:
                    print(f"error: invalid, filter by {attr} can only be [\"open\", \"start\", \"pause\"]",
                            file=sys.stderr)
                    sys.exit(-1)
                if task["state"] != val:
                    return True
            else:
                val = convert_attr_val(attr, val)
                if task[attr] != val:
                    return True
        else:
            print(f"error: unknown arg '{fltr}'", file=sys.stderr)
            sys.exit(-1)

    return False

def find_free_id(task_ids):
    for i in range(1, 1000):
        if i not in task_ids:
            return i
    1

def map_ids(task_ids, ref_ids):
    return dict(zip(task_ids, ref_ids))

async def main():
    val = str('127.0.0.1:23330')
    allowed_states = ["start", "pause", "stop", "open"]

    for i in range(1, len(sys.argv)):
        if sys.argv[i] == "-e":
            val = sys.argv[i+1]
            del sys.argv[i]
            del sys.argv[i]
            break
    
    server_name, port = val.split(':')
    
    refids = await api.get_ref_ids(server_name, port)
    free_ids = []
    tasks = []
    for refid in refids:
        tasks.append(await api.fetch_task(refid, server_name, port))
        free_ids.append(find_free_id(free_ids))

    data = map_ids(free_ids, refids)

    workspace = await api.get_workspace(server_name, port)

    if len(sys.argv) == 1:
        await show_active_tasks(workspace, server_name, port)
        return 0

    if any(x in ["-h", "--help", "help"] for x in sys.argv):
        print('''USAGE:
    tau [OPTIONS] [SUBCOMMAND]

OPTIONS:
    -h, --help      Print help information
    -e              RPC endpoint [default: 127.0.0.1:23330]

SUBCOMMANDS:
    add        Add a new task.
    archive    Show completed tasks.
    comment    Write comment for task by id.
    modify     Modify an existing task by id.
    pause      Pause task(s).
    start      Start task(s).
    stop       Stop task(s).
    switch     Switch between configured workspaces.
    show       List filtered tasks.
    export     Save current workspace tasks to a path.
    import     Load current workspace tasks from a path.
    help       Show this help text.

Examples:
    tau add task one due:0312 rank:1.022 project:zk +lol @sk desc:desc +abc +def
    tau add task two rank:1.044 project:cr +mol @up desc:desc2
    tau add task three due:0512 project:zy +trol @kk desc:desc3 +who
    tau 1 modify @upgr due:1112 rank:none
    tau 1 modify -@up
    tau 1 modify -mol -xx
    tau 1,2 modify +dev @erto
    tau 1-3 start
    tau 1 comment "this is an awesome comment"
    tau 2 pause
    tau show @erto state:start  # list started tasks that are assigned to 'erto'
    tau show +dev project:zk    # list tasks with 'dev' tag project 'zk'
    tau switch darkfi           # switch to configured 'darkfi' workspace
    tau archive                 # current month's completed tasks
    tau archive 1122            # completed tasks of Nov. 2022
    tau archive 1122 1          # show info of task completed in Nov. 2022
''')
        return 0
    elif sys.argv[1] == "log":
        if len(sys.argv) == 3:
            timeframe = sys.argv[2]
        else:
            timeframe = None
        await show_log(server_name, port, timeframe)
        return 0
    elif sys.argv[1] == "add":
        task_args = sys.argv[2:]
        ref, title = await add_task(task_args, server_name, port)
        if title:
            print(f"Created task ({find_free_id(free_ids)}) ({ref[:7]}) '{title}'.")
        return 0
    elif sys.argv[1] == "archive":
        if len(sys.argv) == 4:
            if len(sys.argv[2]) == 4:
                month = sys.argv[2]
                month_ts = lib.util.month_to_unix(month)
            else:
                print("error: usage format is: tau archive [MONTH] [ID]")
                return -1
                
            archive_refids = await api.get_archive_ref_ids(month_ts, server_name, port)
            afree_ids = []
            atasks = []
            for arefid in archive_refids:
                atasks.append(await api.fetch_archive_task(arefid, month_ts, server_name, port))
                afree_ids.append(find_free_id(afree_ids))

            adata = map_ids(afree_ids, archive_refids)

            if len(sys.argv[3]) < 4:
                try:
                    tid = int(sys.argv[3])
                    arefid = adata[tid]
                except (ValueError, KeyError):
                    print("error: invalid ID", file=sys.stderr)
                    return -1
            else:
                print("error: invalid ID", file=sys.stderr)
                return -1
            
            
            if (errc := await show_archive_task(arefid, month_ts, server_name, port)) < 0:
                return errc
        elif len(sys.argv) == 3:
            if sys.argv[2] == "all":
                await show_deactive_tasks(None, workspace, server_name, port)
            elif len(sys.argv[2]) == 4:
                month = sys.argv[2]
                month_ts = lib.util.month_to_unix(month)
                await show_deactive_tasks(month_ts, workspace, server_name, port)
            elif len(sys.argv[2]) < 4:
                month_ts = lib.util.month_to_unix()
                archive_refids = await api.get_archive_ref_ids(month_ts, server_name, port)
                afree_ids = []
                atasks = []
                for arefid in archive_refids:
                    atasks.append(await api.fetch_archive_task(arefid, month_ts, server_name, port))
                    afree_ids.append(find_free_id(afree_ids))

                adata = map_ids(afree_ids, archive_refids)

                try:
                    tid = int(sys.argv[2])
                    arefid = adata[tid]
                except (ValueError, KeyError):
                    print("error: invalid ID", file=sys.stderr)
                    return -1
                
                if (errc := await show_archive_task(arefid, month_ts, server_name, port)) < 0:
                    return errc
            else:
                print("error: month must be of format MMYY")
                return -1
        else:
            month_ts = lib.util.month_to_unix()
            await show_deactive_tasks(month_ts, workspace, server_name, port)
        
        return 0
    elif sys.argv[1] == "show":
        if len(sys.argv) > 2:
            filters = sys.argv[2:]
            list_tasks(tasks, workspace, filters)
        else:
            await show_active_tasks(workspace, server_name, port)
        return 0
    elif sys.argv[1] == "switch":
        if not len(sys.argv) == 3:
            print("Error: you must provide workspace name")
            return 0
        if not await api.switch_workspace(sys.argv[2], server_name, port):
            print(f"Error: Workspace \"{sys.argv[2]}\" is not configured.")
        else:
            print(f"You are now on \"{sys.argv[2]}\" workspace.")
        return 0
    elif sys.argv[1] == "export":
        if len(sys.argv) == 2:
            path = "~/.local/share/darkfi"
        else:
            path = sys.argv[2]
        if await api.export_to(path, server_name, port):
            print(f"Exported tasks successfuly to {path}")
        return 0
    elif sys.argv[1] == "import":
        if len(sys.argv) == 2:
            path = "~/.local/share/darkfi"
        else:
            path = sys.argv[2]
        if await api.import_from(path, server_name, port):
            print(f"Imported tasks successfuly from {path}")
        return 0

    try:
        id = sys.argv[1]
        subcommands = ["modify", "comment"]
        if any(id in ls for ls in [allowed_states, subcommands]):
            user_input = input("This command has no filter, and will modify all tasks. Are you sure? [y/N] ")
            if user_input.lower() in ['y', 'yes']:
                refid = list(refids)
                args = sys.argv[1:]
            else:
                print("Command prevented from running.")
                exit(-1)
        elif any(id == rfid[:len(id)] for rfid in refids if len(id) > 2):
            refid = []
            for rid in refids:
                if id == rid[:len(id)]:
                    refid.append(rid)
            args = sys.argv[2:]
        else:
            lines = id.split(',')
            numbers = []
            for line in lines:
                if line == '':
                    continue
                elif '-' in line:
                    t = line.split('-')
                    numbers += range(int(t[0]), int(t[1]) + 1)
                else:
                    numbers.append(int(line))
            refid = []
            for i in numbers:
                refid.append(data[i])
            args = sys.argv[2:]
    except (ValueError, KeyError):
        print("error: invalid ID", file=sys.stderr)
        return -1
    except EOFError:
        print('\nOperation is cancelled')
        return -1

    if not args:
        for rid in refid:
            await show_task(rid, server_name, port)
        return 0
    
    subcmd, args = args[0], args[1:]

    if subcmd == "modify":
        if not args:
            print("Error: modify subcommand must have at least one argument.")
            exit(-1)
        for rid in refid:
            if (errc := await modify_task(rid, args, server_name, port)) < 0:
                return errc
            time.sleep(0.1)
            await show_task(rid, server_name, port)
    elif subcmd in allowed_states:
        status = subcmd
        for rid in refid:
            if (errc := await change_task_status(rid, status, server_name, port)) < 0:
                return errc
            time.sleep(0.1)
    elif subcmd == "comment":
        for rid in refid:
            if (errc := await comment(rid, args, server_name, port)) < 0:
                return errc
    else:
        print(f"error: unknown subcommand '{subcmd}'")
        return -1

    return 0

asyncio.run(main())

