from __future__ import annotations
from . import command, photo_fn
from .bsh_parser import parse
from .scale import rescale_image, leonzegt_text
from io import BytesIO
from typing import Any, Callable, Dict, Sequence
import typing
import io
import sys
import inspect
from telegram import Bot, Update
if typing.TYPE_CHECKING:
    from main import Bea

#text_message_schema = Schema({object: object,
#                              "text": str,
#                              "username": Optional(str),
#                              "first_name": str,
#                              "last_name": Optional(str)

commands: Dict[str, Callable[[Bea, Bot, Update, Any], Any]] = {}

def bsh_command(name) -> Callable:
    def decorator(fn) -> Callable:
        fn.bsh_cmd_name = name
        return fn
    return decorator

@bsh_command('reify')
def reify(bea: Bea, bot: Bot, update: Update, stdin: Any, *args: Sequence[Any]) -> None:
    if not args:
        raise ValueError('no command to reify specified')
    if args[0] not in commands:
        raise ValueError(f'unknown command {args[0]!r}')
    return commands[args[0]]

@bsh_command('xargs')
def xargs(bea: Bea, bot: Bot, update: Update, stdin: Any, *args: Sequence[Any]) -> None:
    if not args:
        raise ValueError('no command specified')
    return args[0](bea, bot, update, None, stdin)

@bsh_command('leon-zegt')
def leon_zegt(bea, bot, update, stdin, *args):
    if not args:
        raise ValueError('no text specified')
    text = args[0]
    return leonzegt_text(text)

@bsh_command('extract-image')
def extract_photo(bea, bot, update, stdin, *args):
    photo = photo_fn(update)

    buf = BytesIO()

    fl = photo.get_file()
    fl.download(out=buf)
    buf.seek(0)
    return buf

@bsh_command('rescale-image')
def rescale_image_cmd(bea, bot, update, stdin, *args):
    if not hasattr(stdin, 'read'):
        raise ValueError('input to rescale-image is no file-like object.')
    return rescale_image(stdin, '.png')

@bsh_command('send-image')
def send_image(bea, bot, update, stdin, *args):
    if not hasattr(stdin, 'read'):
        raise ValueError('input to send-image is no file-like object.')
    
    update.message.reply_photo(photo=stdin, quote=False)

@bsh_command('get-member')
def get_member(bea, bot, update, stdin, *args):
    if not args:
        raise ValueError('no member name supplied')
    if not hasattr(stdin, args[0]):
        raise ValueError(f'input object has no member named {args[0]!r}')

    return getattr(stdin, args[0])
    

@bsh_command('extract-message')
def extract_message(bea, bot, update, stdin, *args):
    if args:
        raise ValueError("expected no arguments")
    m = update.message
    return 
    

@command('^bsh> (.+)', pass_groups=True, prefix=False)
def bsh_run(bea: Bea, bot: Bot, update: Update, groups: Sequence[str]) -> None:
    try:
        *subshells, shell = parse(groups[0])
    except SyntaxError as e:
        return bea.reply(f"invalid syntax: {e.msg}")

    subshell_results = []

    def run_shell(fragments):
        res = update
        for fragment in fragments:
            cmd_name, *args = fragment
            args = [arg if not isinstance(arg, int) else subshell_results[arg - 1] for arg in args]
            cmd = commands.get(cmd_name)
            if not cmd:
                raise ValueError(f"unknown command {cmd_name!r}")
            try:
                res = cmd(bea, bot, update, res, *args)
            except ValueError as e:
                raise ValueError(f"{cmd_name}: {e}")
        return res

    try:
        for subshell in subshells:
            subshell_results.append(run_shell(subshell))

        res = run_shell(shell)
    except ValueError as e:
        return bea.reply(f"error while evaluating command: {e}")

    if isinstance(res, io.IOBase):
        update.message.reply_photo(photo=res, quote=False)
    elif res is not None:
        bea.reply(f'Result: {res!r}')

def init(bea, config):
    mod = sys.modules[__name__]
    for (name, val) in inspect.getmembers(mod):
        if hasattr(val, "bsh_cmd_name"):
            commands[val.bsh_cmd_name] = val
    return [bsh_run]
