import shlex
from dataclasses import dataclass

from wurker.models import WurkerCommand, FILLMAP


class Args:
    """A generic arguments class like argparse.Namespace."""

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)


@dataclass
class BotArgs:
    """Encapsulates all possible arguments like a rigid argparse.Namespace."""

    # Need to keep local paths straight
    app_root: str|None = None
    subcommand: str|None = None

    # Serve-specific
    return_result: bool = True

    # Logging stuff
    log: str = "syslog"
    loglevel: str = "debug"

    # Show command
    command: str|None = None
    bee: str|None = None
    cron: str|None = None
    supervised: bool = False
    scheduled: bool = False
    ondemand:bool = False
    hive: bool = False
    any: bool = True
    widowed: bool = False

    # Edit command
    option: str|None = None
    key: list|None = None
    json: str|None = None

    # Control command
    action: str|None = None

    # Report command
    start: str = None
    end: str = None
    page: int = None
    page_limit: int = None
    sort: str = None
    sort_order: str = None

    # Colony option
    drone: str = None

    def __post_init__(self):
        """Set filter args from flags."""

        options = []
        keyed = []

        if self.subcommand == "show":
            options = ["supervised", "scheduled", "ondemand", "hive", "command", "cron", "bee",]
            keyed = ["command", "cron", "bee",]
        elif self.subcommand == "kill":
            options = ["supervised", "scheduled", "ondemand", "hive", "cron", "bee",]
            keyed = ["cron", "bee",]

        for option in options:
            value = getattr(self, option)
            if value:
                self.option = option
                self.key = True if option not in keyed else value


def parse_fillable(wurkman, enforce_required=False):
    """For add and edit functions."""

    model = FILLMAP[wurkman.args.option]["model"]
    fillable = FILLMAP[wurkman.args.option]["fillable"]
    required = FILLMAP[wurkman.args.option]["required"]

    # Validate CLI args against FILLMAP, and cast to the correct type
    kwargs = {}
    for key, val in wurkman.args.cliargs.items():
        if key not in fillable:
            raise ValueError(f"Invalid COLUMN value '{key}'. Use one of: {list(fillable.keys())}")

        if key == "args":
            kwargs[key] = val if isinstance(val, list) or isinstance(val, dict) else shlex.split(val)
        elif fillable[key] == bool:
            kwargs[key] = val if isinstance(val, bool) else True if val.lower() in ["1", "true"] else False
        else:
            kwargs[key] = fillable[key](val)

    # Bees must have a command_id or command_slug
    if wurkman.args.subcommand == "add" and wurkman.args.option == "bee" and "command_id" not in kwargs:
        if "command_slug" not in kwargs:
            raise ValueError(f"Bees require 'command_id' or 'command_slug' COLUMN key-value pair")

        with wurkman.session() as db:
            command = db.query(WurkerCommand).filter(
                WurkerCommand.slug == kwargs["command_slug"],
                WurkerCommand.app_id == wurkman.app_id
            ).first()

        if command is None:
            raise ValueError(f"Wurker command not found: '{kwargs['command_slug']}'")

        kwargs["command_id"] = command.id
        del kwargs["command_slug"]

    if enforce_required:
        missing = required - set(kwargs.keys())
        if missing:
            raise ValueError(f"Missing required COLUMN(s): {missing}")

    kwargs["app_id"] = wurkman.app_id
    return model, kwargs
