from .import enums
from typing import List, Type, Iterable, Optional, overload
import inspect, httpx

def build_from_dict(
    data: dict,
    classes: Optional[Iterable[Type]] = None,
) -> Type:
    if not isinstance(data, dict):
        raise TypeError("data must be dict")

    if classes is None:
        classes = [
            obj for obj in globals().values()
            if isinstance(obj, type)
        ]

    best_match = None
    best_score = -1

    for cls in classes:
        try:
            sig = inspect.signature(cls.__init__)
        except (TypeError, ValueError):
            continue

        params = list(sig.parameters.values())[1:]

        param_names = {
            p.name
            for p in params
            if p.kind in (p.POSITIONAL_OR_KEYWORD, p.KEYWORD_ONLY)
        }

        if not param_names:
            continue

        score = len(param_names.intersection(data.keys()))

        if score == 0:
            continue

        required = {
            p.name
            for p in params
            if p.default is inspect._empty
        }

        if not required.issubset(data.keys()):
            continue

        if score > best_score:
            best_score = score
            best_match = cls

    if best_match is None:
        raise ValueError("No matching class found")

    return best_match

class BaseModel:

    def to_dict(self):
        return self._serialize(self)

    def _serialize(self, value):
        if isinstance(value, (str, int, float, bool, type(None))):
            return value

        if isinstance(value, dict):
            return {
                k: self._serialize(v)
                for k, v in value.items()
                if v is not None
            }

        if isinstance(value, (list, tuple, set)):
            return [
                self._serialize(item)
                for item in value
                if item is not None
            ]

        if hasattr(value, 'get_'):
            return value.get_()

        if hasattr(value, "__dict__"):
            return {
                k: self._serialize(v)
                for k, v in vars(value).items()
                if not k.startswith("_") and v is not None
            }

        return value

class Chat(BaseModel):
    def __init__(
            self,
            chat_id: str = None,
            chat_type: enums.ChatTypeEnum = None,
            user_id: str = None,
            first_name: str = None,
            last_name: str = None,
            title: str = None,
            username: str = None,
    ):
        self.id = chat_id
        self.chat_type = chat_type
        self.user_id = user_id
        self.first_name = first_name
        self.last_name = last_name
        self.title = title
        self.username = username
    
    @property
    def type(self):
        return self.chat_type

    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class File(BaseModel):
    def __init__(
            self,
            file_id: str = None,
            file_name: str = None,
            size: str = None,
    ):
        self.file_id = file_id
        self.file_name = file_name
        self.size = size
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)
            setattr(self, key, obj)

class ForwardedFrom(BaseModel):
    def __init__(
            self,
            type_from: enums.ForwardedFromEnum = None,
            message_id: str = None,
            from_chat_id: str = None,
            from_sender_id:str = None,
    ):
        self.type_from = type_from
        self.message_id = message_id
        self.from_chat_id = from_chat_id
        self.from_sender_id = from_sender_id
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class MessageTextUpdate(BaseModel):
    def __init__(
            self,
            message_id: str = None,
            text: str = None,
    ):
        self.message_id = message_id
        self.text = text
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class Bot(BaseModel):
    def __init__(
            self,
            bot_id: str = None,
            bot_title: str = None,
            avatar: File = None,
            description: str = None,
            username: str = None,
            start_message: str = None,
            share_url: str = None
    ):
        self.bot_id = bot_id
        self.bot_title = bot_title
        self.avatar = avatar
        self.description = description
        self.username = username
        self.start_message = start_message
        self.share_url = share_url
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class BotCommand(BaseModel):
    def __init__(
            self,
            command: str = None,
            description: str = None,
    ):
        self.command = command
        self.description = description
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class Sticker(BaseModel):
    def __init__(
            self,
            sticker_id: str = None,
            file: File = None,
            emoji_character: str = None,
    ):
        self.sticker_id = sticker_id
        self.file = file
        self.emoji_character = emoji_character
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class ContactMessage(BaseModel):
    def __init__(
            self,
            phone_number: str = None,
            first_name: str = None,
            last_name: str = None,
    ):
        self.phone_number = phone_number
        self.first_name = first_name
        self.last_name = last_name
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class PollStatus(BaseModel):
    def __init__(
            self,
            state: enums.PollStatusEnum = None,
            selection_index: int = None,
            percent_vote_options: List[int] = None,
            total_vote: int = None,
            show_total_votes: bool = None,
    ):
        self.state = state
        self.selection_index = selection_index
        self.percent_vote_options = percent_vote_options
        self.total_vote = total_vote
        self.show_total_votes = show_total_votes
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class Poll(BaseModel):
    def __init__(
            self = None,
            question: str = None,
            options: List[str] = None,
            poll_status: PollStatus = None,
    ):
        self.question = question
        self.options = options
        self.poll_status = poll_status
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class Location(BaseModel):
    def __init__(
            self,
            longitude: str = None,
            latitude: str = None,
    ):
        self.longitude = longitude
        self.latitude = latitude
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class ButtonSelectionItem(BaseModel):
    def __init__(
            self,
            text: str = None,
            image_url: str = None,
            type: enums.ButtonSelectionTypeEnum = None,
    ):
        self.text = text
        self.image_url = image_url
        self.type = type
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class ButtonSelection(BaseModel):
    def __init__(
            self,
            selection_id: str = None,
            search_type: str = None,
            get_type: str = None,
            items: List[ButtonSelectionItem] = None,
            is_multi_selection: bool = None,
            columns_count: str = None,
            title: str = None,
    ):
        self.selection_id = selection_id
        self.search_type = search_type
        self.get_type = get_type
        self.items = items
        self.is_multi_selection = is_multi_selection
        self.columns_count = columns_count
        self.title = title
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class ButtonCalendar(BaseModel):
    def __init__(
            self,
            default_value: Optional[str] = None,
            type: enums.ButtonCalendarTypeEnum = None,
            min_year: str = None,
            max_year: str = None,
            title: str = None,
    ):
        self.default_value = default_value
        self.type = type
        self.min_year = min_year
        self.max_year = max_year
        self.title = title
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class ButtonNumberPicker(BaseModel):
    def __init__(
            self,
            min_value: str = None,
            max_value: str = None,
            default_value: Optional[str] = None,
            title: str = None,
    ):
        self.min_value = min_value
        self.max_value = max_value
        self.default_value = default_value
        self.title = title
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class ButtonStringPicker(BaseModel):
    def __init__(
            self,
            items: List[str] = None,
            default_value: Optional[str] = None,
            title: Optional[str] = None,
    ):
        self.items = items
        self.default_value = default_value
        self.title = title
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class ButtonTextbox(BaseModel):
    def __init__(
            self,
            type_line: enums.ButtonTextboxTypeLineEnum = None,
            type_keypad: enums.ButtonTextboxTypeKeypadEnum = None,
            place_holder: Optional[str] = None,
            title: Optional[str] = None,
            default_value: Optional[str] = None,
    ):
        self.type_line = type_line
        self.type_keypad = type_keypad
        self.place_holder = place_holder
        self.title = title
        self.default_value = default_value
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class ButtonLocation(BaseModel):
    def __init__(
            self,
            default_pointer_location: Location = None,
            default_map_location: Location = None,
            type: enums.ButtonLocationTypeEnum = None,
            title: Optional[str] = None,
            location_image_url: str = None,

    ):
        self.default_pointer_location = default_pointer_location
        self.default_map_location = default_map_location
        self.type = type
        self.title = title
        self.location_image_url = location_image_url
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class AuxData(BaseModel):
    def __init__(
            self,
            start_id: str = None,
            button_id: str = None,
    ):
        self.start_id = start_id
        self.button_id = button_id
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class Button(BaseModel):
    def __init__(
            self,
            id: str = None,
            type: enums.ButtonTypeEnum = None,
            button_text: str = None,
            button_selection: ButtonSelection = None,
            button_calendar: ButtonCalendar = None,
            button_number_picker: ButtonNumberPicker = None,
            button_string_picker: ButtonStringPicker = None,
            button_location: ButtonLocation = None,
            button_textbox: ButtonTextbox = None,
    ):
        self.id = id
        self.type = type
        self.button_text = button_text
        self.button_selection = button_selection
        self.button_calendar = button_calendar
        self.button_number_picker = button_number_picker
        self.button_string_picker = button_string_picker
        self.button_location = button_location
        self.button_textbox = button_textbox
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class KeypadRow(BaseModel):
    def __init__(
            self,
            buttons: List[Button] = None,
    ):
        self.buttons = buttons
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class Keypad(BaseModel):
    def __init__(
            self,
            rows: List[KeypadRow] = None,
            resize_keyboard: bool = None,
            one_time_keyboard: bool = None,
    ):
        self.rows = rows
        self.resize_keyboard = resize_keyboard
        self.one_time_keyboard = one_time_keyboard
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class MessageKeypadUpdate(BaseModel):
    def __init__(
            self,
            message_id: str = None,
            inline_keypad: Keypad = None,
    ):
        self.message_id = message_id
        self.inline_keypad = inline_keypad
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class Message(BaseModel):
    def __init__(
            self,
            chat: Chat = None,
            message_id: str = None,
            text: str = None,
            time: int = None,
            is_edited: bool = None,
            sender_type: enums.MessageSenderEnum = None,
            sender_id: str = None,
            aux_data: AuxData = None,
            file: File = None,
            reply_to_message_id: str = None,
            forwarded_from: ForwardedFrom = None,
            forwarded_no_link: str = None,
            location: Location = None,
            sticker: Sticker = None,
            contact_message: ContactMessage = None,
            poll: Poll = None,
    ):
        self.chat = chat
        self.message_id = message_id
        self.text = text
        self.time = time
        self.is_edited = is_edited
        self.sender_type = sender_type
        self.sender_id = sender_id
        self.aux_data = aux_data
        self.file = file
        self.reply_to_message = reply_to_message_id
        self.forwarded_from = forwarded_from
        self.forwarded_no_link = forwarded_no_link
        self.location = location
        self.sticker = sticker
        self.contact_message = contact_message
        self.poll = poll
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

    async def reply(
            self,
            text: str = None,
            chat_keypad: Keypad = None,
            disable_notification: bool = False,
            inline_keypad: Keypad = None,
            reply_to_message_id: str = None,
            chat_keypad_type: enums.ChatKeypadTypeEnum = None,
    ):
        ...

class Update(BaseModel):
    def __init__(
            self,
            type: enums.UpdateTypeEnum = None,
            chat_id: str = None,
            removed_message_id: Optional[str] = None,
            new_message: Message = None,
            updated_message: Optional[Message] = None,
    ):
        self.type = type
        self.chat_id = chat_id
        self.removed_message_id = removed_message_id
        self.new_message = new_message
        self.updated_message = updated_message
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class InlineMessage(BaseModel):
    def __init__(
            self,
            sender_id: str = None,
            text: str = None,
            file: Optional[File] = None,
            location: Optional[Location] = None,
            aux_data: Optional[AuxData] = None,
            message_id: str = None,
            chat_id: str = None,
    ):
        self.sender_id = sender_id
        self.text = text
        self.file = file
        self.location = location
        self.aux_data = aux_data
        self.message_id = message_id
        self.chat_id = chat_id
    
    def _builder(self, dict_: dict):
        for key in dict_.keys():
            if isinstance(dict_.get(key), dict):
                obj = build_from_dict(dict_.get(key))()
                obj._builder(dict_.get(key))
            else:
                obj = dict_.get(key)

            setattr(self, key, obj)

class AutoMaticEndPoint:
    def __init__(
            self,
            automatic: bool,
            default: str
    ):
        self.auto = httpx.get('https://api.ipify.org').text if automatic else default

class WebHook:
    def __init__(
            self,
            AutomaticEndPoint: AutoMaticEndPoint,
            ReceiveUpdateEndPoint: str = None,
            ReceiveInlineMessageEndPoint: str = None,
            ReceiveQueryEndPoint: str = None,
            GetSelectionItemEndPoint: str = None,
            SearchSelectionItemsEndPoint: str = None,
    ):
        self.ReceiveUpdateEndPoint = ReceiveUpdateEndPoint
        self.ReceiveInlineMessageEndPoint = ReceiveInlineMessageEndPoint
        self.ReceiveQueryEndPoint = ReceiveQueryEndPoint
        self.GetSelectionItemEndPoint = GetSelectionItemEndPoint
        self.SearchSelectionItemsEndPoint = SearchSelectionItemsEndPoint
        self.EndPoint = AutomaticEndPoint.auto

class Callback_Query:
    ...
