###############################################################################
#
# (C) Copyright 2025 EVERYSK TECHNOLOGIES
#
# This is an unpublished work containing confidential and proprietary
# information of EVERYSK TECHNOLOGIES. Disclosure, use, or reproduction
# without authorization of EVERYSK TECHNOLOGIES is prohibited.
#
###############################################################################
from inspect import isroutine
from typing import TYPE_CHECKING, Any, TypeVar

from everysk.core.mapping.fields import BaseMappingField, get_field_type

MT = TypeVar('MT', bound='BaseMapping')
if TYPE_CHECKING:
    from everysk.core.mapping.base import BaseMapping


class BaseMappingMetaClass(type):
    # Method called when a class is created in the Python runtime
    def __new__(cls, name: str, bases: tuple[type, ...], attrs: dict[str, Any]) -> MT:
        # We need to ensure that __annotations__ exists so the code doesn't fail
        if '__annotations__' not in attrs or attrs['__annotations__'] is None:
            attrs['__annotations__'] = {}

        if '__attributes__' not in attrs or attrs['__attributes__'] is None:
            attrs['__attributes__'] = set()

        # Attributes that are inside annotations -> var: str or var: str = 'value'
        # Attributes that are not inside annotations -> var = 'value'
        # We need to get both
        for key in attrs['__annotations__'].keys() | attrs.keys():
            # Discard internal attributes
            if not key.startswith('__'):
                default_value = attrs.get(key)
                if not isroutine(default_value) and not isinstance(default_value, property):
                    if not isinstance(default_value, BaseMappingField):
                        # If the attribute is not inside annotations (Ex: var = 'value'), we add it
                        if key not in attrs['__annotations__']:
                            field_type = get_field_type(type(default_value))
                            attrs['__annotations__'][key] = field_type
                        else:
                            field_type = get_field_type(attrs['__annotations__'][key])

                        # Change the attribute to a BaseMappingField descriptor
                        attrs[key] = BaseMappingField(default=default_value, field_name=key, field_type=field_type)
                    else:
                        # By default, descriptors don't add themselves to annotations
                        field_type = default_value.field_type
                        attrs['__annotations__'][key] = field_type

        obj: BaseMapping = super().__new__(cls, name, bases, attrs)
        obj._generate_attributes()  # noqa: SLF001
        return obj

    def __setattr__(cls: 'BaseMapping', name: str, value: Any) -> None:
        """
        Set attribute in the class, used to define new attributes dynamically.

        Args:
            cls (BaseMapping): The class in which to set the attribute.
            name (str): The name of the attribute to set.
            value (Any): The value to set for the attribute.
        """
        # Internal attributes are set normally
        # They are __attributes__, __annotations__, etc.
        if name.startswith('__'):
            return super().__setattr__(name, value)

        if name not in cls.__dict__:
            # If the attribute is not defined, we add it
            field_type = get_field_type(type(value))
            cls.__annotations__[name] = field_type
            cls.__attributes__.add(name)
        else:
            field_type = cls.__dict__[name].field_type

        # Value is always wrapped in a BaseMappingField descriptor to handle
        # getting and setting correctly in the instance
        value = BaseMappingField(default=value, field_name=name, field_type=field_type)
        return super().__setattr__(name, value)

    def __delattr__(cls: 'BaseMapping', name: str) -> None:
        """
        Delete attribute from the class and remove it from internal tracking.

        Args:
            cls (BaseMapping): The class from which to delete the attribute.
            name (str): The name of the attribute to delete.
        """
        if name in cls.__annotations__:
            del cls.__annotations__[name]

        if name in cls.__attributes__:
            cls.__attributes__.remove(name)

        return super().__delattr__(name)
