import gc
import inspect
from collections import defaultdict

import types
import six  # se estiver instalado

"""
Esse codigo vai ser usado para verificar se existe alguma variável que estaria
ligada a outra variável, de forma que mudando a primeira, a segunda também
tenha o seu valor mudado.

Esse codigo foi criado para investigar o porque das pressões no chute inicial do
sistema super simples (chute este que foi passado como argumento para o solver)
estavam sendo settadas como = 0 , sendo que somente a pressão de saída deveria
ser numericamente igual a zero
"""

IMUTAVEIS = (int, float, str, bool, tuple, frozenset, bytes)


TIPOS_PROIBIDOS = (
    types.ModuleType,
    types.FunctionType,
    types.MethodType,
    type,
    type(six),  # modulo six
)


def is_mutavel(obj):
    """Retorna True se o objeto for potencialmente mutável."""
    if isinstance(obj, IMUTAVEIS):
        return False
    return True


def iter_subobjetos(obj):
    """Itera recursivamente sobre subobjetos dentro de listas/dicts/sets/tuplas."""
    if isinstance(obj, dict):
        for k, v in obj.items():
            yield k
            yield v

    elif isinstance(obj, (list, set, frozenset)):
        for item in obj:
            yield item

    elif isinstance(obj, tuple):
        for item in obj:
            yield item


# def get_atributos(obj):
#     """Coleta atributos via __dict__ e __slots__."""
#     atributos = {}

#     # Atributos normais
#     if hasattr(obj, "__dict__"):
#         atributos.update(vars(obj))

#     # Atributos de classe declarados em __slots__
#     if hasattr(obj, "__slots__"):
#         for slot in obj.__slots__:
#             if hasattr(obj, slot):
#                 atributos[slot] = getattr(obj, slot)

#     # Atributos de classe estáticos
#     for nome, valor in inspect.getmembers(obj.__class__):
#         if not nome.startswith("__") and not callable(valor):
#             atributos.setdefault(nome, valor)

#     return atributos


def get_atributos(obj):
    atributos = {}

    if isinstance(obj, types.ModuleType):
        return

    # Atributos normais
    if hasattr(obj, "__dict__"):
        atributos.update(obj.__dict__)

    # # Slots
    # slots = getattr(obj, "__slots__", None)

    try:
        slots = obj.__slots__
    except Exception:
        return {}  # não mexer nesses objetos

    if slots:
        # Normalizar slots
        if isinstance(slots, str):
            slots = (slots,)
        elif isinstance(slots, (list, tuple, set)):
            slots = tuple(slots)
        elif isinstance(slots, dict):
            slots = tuple(slots.keys())
        else:
            # tipo desconhecido, ignora
            slots = ()

        for slot in slots:
            try:
                atributos[slot] = getattr(obj, slot)
            except AttributeError:
                pass

    return atributos


def deep_scan(obj, dono, caminho, refs, visitados):
    """
    Procura recursivamente por objetos mutáveis e registra suas referências.
    """

    if isinstance(obj, TIPOS_PROIBIDOS):
        return  # não continue escaneando esses tipos

    obj_id = id(obj)
    if obj_id in visitados:
        return
    visitados.add(obj_id)

    if is_mutavel(obj):
        refs[obj_id].append((dono, caminho))

    # Iterar atributos internos
    for nome, valor in get_atributos(obj).items():
        deep_scan(valor, dono, f"{caminho}.{nome}", refs, visitados)

    # Iterar subobjetos contidos (lista, dict, etc.)
    for i, sub in enumerate(iter_subobjetos(obj)):
        deep_scan(sub, dono, f"{caminho}[{i}]", refs, visitados)


def encontrar_compartilhamentos(modulo_alvo=None):
    """
    Encontra objetos mutáveis compartilhados entre TODAS as instâncias
    definidas pelo usuário. Ignora objetos padrão da biblioteca Python.
    """
    refs = defaultdict(list)
    visitados = set()

    for obj in gc.get_objects():
        # Filtrar para pegar apenas objetos definidos no seu módulo
        if modulo_alvo:
            try:
                if getattr(obj.__class__, "__module__", None) != modulo_alvo:
                    continue
            except:
                continue

        if isinstance(obj, types.ModuleType):
            return

        # Só inspecionar objetos com atributos (instâncias)
        if not hasattr(obj, "__dict__") and not hasattr(obj, "__slots__"):
            continue

        deep_scan(obj, obj, obj.__class__.__name__, refs, visitados)

    # Só manter objetos mutáveis com mais de uma referência
    compartilhados = {
        obj_id: destinos for obj_id, destinos in refs.items() if len(destinos) > 1
    }

    return compartilhados


def relatorio_compartilhamentos(modulo=None):
    problemas = encontrar_compartilhamentos(modulo)

    if not problemas:
        print("\n\n✔ Nenhum objeto mutável compartilhado encontrado!\n\n")
        return

    print("\n⚠️ Objetos mutáveis compartilhados encontrados:\n")
    for obj_id, refs in problemas.items():
        print(f"Objeto mutável: id={obj_id}")
        for dono, caminho in refs:
            print(f"  - Em {dono} via caminho {caminho}")
        print("-" * 60)
