import base64
import datetime
import inspect

from telethon import functions, types
from telethon.tl.tlobject import TLObject, TLRequest

from ..errors import AccessError, InputError
from .utils import validate

class TL:
    namespaces = {key for key, value in vars(functions).items() if (
        inspect.ismodule(value) and value.__name__.startswith("telethon.tl.functions.") and key != "auth"
    )}
    blocked = {
        "account.GetPassword",
        "account.GetPasswordSettings",
        "account.GetTmpPassword",
        "account.GetAuthorizations",
        "account.ResetAuthorization",
        "account.ResetWebAuthorization",
        "account.ResetWebAuthorizations",
        "account.UpdatePasswordSettings",
        "account.GetWebAuthorizations",
        "account.GetPasskeys",
        "account.AcceptAuthorization",
        "account.ChangeAuthorizationSettings",
        "account.SetAuthorizationTTL",
        "account.ResetPassword",
        "account.CancelPasswordEmail",
        "account.ConfirmPasswordEmail",
        "account.ResendPasswordEmail",
        "account.DeclinePasswordReset",
        "bots.ExportBotToken",
        "bots.AnswerWebhookJSONQuery",
    }

    @classmethod
    def request(cls, method, data):
        if not isinstance(method, str) or method.count(".") != 1: raise AccessError("The request is not available to modules")

        namespace, name = method.split(".")
        if namespace not in cls.namespaces or method in cls.blocked or not name.isidentifier() or name.startswith("_"):
            raise AccessError("The request is not available to modules")

        target = getattr(getattr(functions, namespace, None), name + "Request", None)
        if target is None or not inspect.isclass(target) or not issubclass(target, TLRequest): raise InputError("Unknown Telegram request")

        return cls.construct(target, data)

    @classmethod
    def construct(cls, target, data, depth=0):
        if not isinstance(data, dict): raise InputError("Telegram parameters are required")

        values = {key: cls.input(value, depth + 1) for key, value in data.items()}

        try:
            inspect.signature(target).bind(**values)
            return target(**values)
        except (TypeError, ValueError) as e: raise InputError("Invalid Telegram parameters") from e

    @classmethod
    def input(cls, value, depth=0):
        if depth > 24: raise InputError("Telegram parameters are nested too deeply")
        if isinstance(value, list): return [cls.input(item, depth + 1) for item in value]
        if not isinstance(value, dict): return value

        if set(value) == {"bytes"}:
            try: return base64.b64decode(value["bytes"], validate=True)
            except (ValueError, TypeError) as e: raise InputError("Invalid bytes") from e
        if set(value) == {"date"}:
            if type(value["date"]) is not int: raise InputError("Invalid date")

            try: return datetime.datetime.fromtimestamp(value["date"], datetime.timezone.utc)
            except (ValueError, OverflowError, OSError) as error: raise InputError("Invalid date") from error
        name = value.get("_")
        if not isinstance(name, str) or any(not item.isidentifier() or item.startswith("_") for item in name.split(".")):
            raise InputError("Invalid Telegram type")
        if name.count(".") > 1: raise InputError("Invalid Telegram type")
        target = types
        for part in name.split("."): target = getattr(target, part, None)
        if target is None or not inspect.isclass(target) or not issubclass(target, TLObject) or issubclass(target, TLRequest):
            raise InputError("The type is not available to modules")

        return cls.construct(target, {key: item for key, item in value.items() if key != "_"}, depth)

    @classmethod
    def output(cls, value, depth=0):
        if depth > 24: raise InputError("Result nesting is too deep")

        if hasattr(value, "to_dict"): value = value.to_dict()
        if isinstance(value, bytes): return {"bytes": base64.b64encode(value).decode()}
        if isinstance(value, datetime.datetime): return {"date": int(value.timestamp())}
        if isinstance(value, dict): return {key: cls.output(item, depth + 1) for key, item in value.items()}
        if isinstance(value, (list, tuple)): return [cls.output(item, depth + 1) for item in value]

        validate(value)

        return value
