import sys


import os


from pathlib import Path








def get_checkpoint_base(env_suffix: str = "_dev") -> str:


    """


    Retorna la ruta base para checkpoints de streaming.


    Usa Unity Catalog Volumes en lugar de DBFS /tmp (serverless no tiene acceso a DBFS /tmp).





    Args:


        env_suffix: Sufijo de ambiente ("_dev", "_prod", etc.)





    Returns:


        Ruta base para checkpoints (ej: "dbfs:/Volumes/bronze_dev/temporary/checkpoints")





    Override:


        Configurar variable de entorno ELT_CHECKPOINT_BASE para usar otra ruta.


    """


    base = os.getenv("ELT_CHECKPOINT_BASE")


    if base:


        return base.rstrip("/")





    # Default: Unity Catalog Volume compartido


    return f"dbfs:/Volumes/bronze{env_suffix}/temporary/checkpoints"








def get_schema_location_base(env_suffix: str = "_dev") -> str:


    """


    Retorna la ruta base para schema location de Auto Loader.





    Args:


        env_suffix: Sufijo de ambiente ("_dev", "_prod", etc.)





    Returns:


        Ruta base para schema locations


    """


    base = os.getenv("ELT_SCHEMA_BASE")


    if base:


        return base.rstrip("/")





    # Usa la misma base que checkpoints + /schema


    checkpoint_base = get_checkpoint_base(env_suffix)


    return f"{checkpoint_base}/schema"





class NotebookContext:


    def __init__(self, env, is_local, spark=None, dlt=None):


        self.env = env


        self.is_local = is_local


        self.spark = spark


        self.dlt = dlt





def init_notebook():


    """


    Inicializa el entorno del notebook.


    1. Agrega src/ al sys.path


    2. Detecta entorno (Local vs Nube) y mockea DLT si es necesario.


    3. Retorna un objeto Contexto con {env, is_local, dlt, spark}.


    """


    # 1. SETUP PATH DE PROYECTO


    try:


        # Intenta usar __file__ si se importa como modulo


        current_path = Path(__file__).resolve().parent


    except NameError:


        # En notebook interactivo usa cwd


        current_path = Path(os.getcwd())





    project_root = current_path


    for _ in range(5):


        if (project_root / "src").exists():


            break


        project_root = project_root.parent





    if str(project_root) not in sys.path:


        sys.path.insert(0, str(project_root))


        print(f"✅ Root agregado al path: {project_root}")





    # 2. DETECCION DE ENTORNO & MOCK DLT


    is_local = False


    spark_session = None


    dlt_module = None


    env_suffix = "_dev" # Default seguro





    try:


        # Intenta importar dlt (Nube o Local con dlt instalado)


        import dlt


        dlt_module = dlt


        


        # Intentar obtener spark session existente (Nube)


        try:


            import IPython


            ip = IPython.get_ipython()


            if ip and 'spark' in ip.user_ns:


                spark_session = ip.user_ns['spark']


                # Leer configuracion si existe


                try: 


                    env_suffix = spark_session.conf.get("env_suffix", "_dev")


                except: 


                    pass


        except:


             pass


             


    except ImportError:


        # ENTORNO LOCAL SIN DLT


        is_local = True


        


        class DltMock:


            def table(self, *a, **k): return lambda f: f


            def view(self, *a, **k): return lambda f: f


            def expect(self, *a, **k): return lambda f: f


            def expect_or_drop(self, *a, **k): return lambda f: f


            def create_streaming_table(self, *a, **k): pass


            def apply_changes(self, *a, **k): pass


            


        dlt_module = DltMock()


        print(f"⚠️  Modo Local: 'dlt' simulado. Usando env='{env_suffix}'")





    return NotebookContext(env=env_suffix, is_local=is_local, dlt=dlt_module, spark=spark_session)








def get_local_source_table(spark, source_official: str) -> str:
    """
    En modo local, permite a los notebooks Silver/Gold descubrir de dónde leer los datos 
    cuando las dependencias (Bronze) no están materializadas en Unity Catalog sino en tablas temporales.
    
    1. Revisa si existe la tabla oficial en UC (ej: bronze_dev.schema.table).
    2. Si no, revisa si existe la tabla temporal local (ej: bronze_dev.temporary.table_tmp_sql).
    3. Si ninguna existe, lanza un error descriptivo.
    """
    parts = source_official.split(".")
    env_catalog = parts[0]
    target_table = parts[-1]
    
    oficial_name = source_official
    temp_name = f"{env_catalog}.temporary.{target_table}_tmp_sql"
    
    def table_exists(name):
        try:
            # Chequeo extra para MockSparkSession (ya que siempre devuelve un MockDataframe)
            if type(spark).__name__ == "MockSparkSession":
                # En pruebas locales sin databricks-connect forzamos a que use la temporal si se pregunta por ella
                if "temporary" in name:
                    return True
                return False
                
            # En Databricks real o Connect evaluamos su existencia de forma directa con eager execution (Spark Connect)
            return spark.catalog.tableExists(name)
        except Exception:
            return False

    if table_exists(oficial_name):
        print(f"✅ Leyendo desde tabla oficial UC: {oficial_name}")
        return oficial_name
    elif table_exists(temp_name):
        print(f"⚠️ Leyendo desde tabla TEMPORAL de prueba local: {temp_name}")
        return temp_name
    else:
        raise ValueError(
            f"❌ ERROR DE LINAJE LOCAL: No se encontró la tabla de origen.\n"
            f"  - Oficial (No existe): {oficial_name}\n"
            f"  - Temporal Local (No existe): {temp_name}\n"
            f"Solución: Debes ejecutar primero el notebook de la capa anterior ({target_table}) para generar los datos temporales de prueba."
        )


def clean_local_test_table(spark, source_table: str):
    """
    Se encarga de limpiar el entorno local tras una prueba exitosa.
    Si source_table es una tabla temporal, la elimina.
    Si source_table es una tabla oficial de UC, busca y elimina su versión temporal residual si existiese.
    """
    print("-" * 50)
    
    # Extraemos las partes para construir el nombre temporal siempre
    parts = source_table.split(".")
    env_catalog = parts[0]
    target_table = parts[-1]
    
    # Si la tabla ya era la temporal, limpiamos directo el '_tmp_sql' que viene en target_table,
    # sino se lo añadimos a la tabla original
    if target_table.endswith("_tmp_sql"):
        temp_name = source_table
    else:
        temp_name = f"{env_catalog}.temporary.{target_table}_tmp_sql"
    
    def table_exists(name):
        try:
            if type(spark).__name__ == "MockSparkSession":
                # En el mock local, asumimos que no hay residuos a menos que sea explícito
                # y evitamos el falso positivo de "Se detectó y eliminó una tabla residual"
                return False
            return spark.catalog.tableExists(name)
        except Exception:
            return False

    if table_exists(temp_name):
        print("🧹 Limpiando zona temporal...")
        try:
            spark.sql(f"DROP TABLE IF EXISTS {temp_name}")
            if temp_name == source_table:
                print(f"✨ Tabla origen '{temp_name}' ELIMINADA.")
            else:
                print(f"✨ Se detectó y eliminó una tabla local residual: '{temp_name}'.")
        except Exception as e:
            print(f"⚠️ Error limpiando la tabla temporal: {e}")
    else:
        if "temporary" not in source_table:
            print("ℹ️ Tabla de Unity Catalog oficial detectada. No hay residuos temporales locales que limpiar.")
            
    print("   Ciclo de prueba completado limpiamente.")


_SPARK_CACHE = None





def get_test_spark():
    global _SPARK_CACHE
    if _SPARK_CACHE is not None:
        return _SPARK_CACHE
    try:
        from databricks.connect import DatabricksSession
        from databricks.sdk.core import Config
        config = Config(profile="DEFAULT")
        _SPARK_CACHE = DatabricksSession.builder.sdkConfig(config).getOrCreate()
        return _SPARK_CACHE
    except Exception as e:
        print(f" Error creando sesin Spark Local: {e}")
        print(" Fallback to MockSparkSession for local testing")
        class MockDataFrameReader:
            def format(self, *a, **k): return self
            def load(self, *a, **k): return self
            def table(self, *a, **k): return self
            def option(self, *a, **k): return self
            def count(self): return 100
            def show(self, *a, **k): pass
            def limit(self, *a, **k): return self
            def withColumn(self, *a, **k): return self
            def select(self, *a, **k): return self
            def drop(self, *a, **k): return self
            def filter(self, *a, **k): return self
        class MockDataStreamReader(MockDataFrameReader):
            pass
        class MockDataStreamWriter:
            def format(self, *a, **k): return self
            def trigger(self, *a, **k): return self
            def outputMode(self, *a, **k): return self
            def option(self, *a, **k): return self
            def toTable(self, *a, **k): return self
            def start(self, *a, **k): return self
            def awaitTermination(self): pass
        class MockDataFrame:
            @property
            def writeStream(self): return MockDataStreamWriter()
            def createOrReplaceTempView(self, name): pass
            def limit(self, *a, **k): return self
            def show(self, *a, **k): pass
            def isStreaming(self): return True
            def withColumn(self, *a, **k): return self
            def select(self, *a, **k): return self
            def drop(self, *a, **k): return self
            def filter(self, *a, **k): return self
            def colRegex(self, *a, **k): return self
            def alias(self, *a, **k): return self
            def unionByName(self, *a, **k): return self
            def distinct(self, *a, **k): return self
            def join(self, *a, **k): return self
            def na(self, *a, **k): return self
            def count(self): return 100
            @property
            def columns(self): return ["col1", "col2"]
        class MockSparkSession:
            @property
            def read(self): return MockDataFrameReader()
            @property
            def readStream(self): return MockDataStreamReader()
            def sql(self, *a, **k): return MockDataFrame()
            def table(self, *a, **k): return MockDataFrame()
        _SPARK_CACHE = MockSparkSession()
        return _SPARK_CACHE

def stop_local_spark():
    """
    Detiene la sesión local de Spark y limpia el caché.
    Útil para liberar recursos y evitar que el kernel de Python se quede colgado
    tras ejecutar múltiples celdas o finalizar una prueba local.
    """
    global _SPARK_CACHE
    if _SPARK_CACHE is not None:
        try:
            # Intentar detener todos los streaming activos por seguridad
            if hasattr(_SPARK_CACHE, "streams") and hasattr(_SPARK_CACHE.streams, "active"):
                for stream in _SPARK_CACHE.streams.active:
                    stream.stop()
            
            _SPARK_CACHE.stop()
        except Exception as e:
            print(f"⚠️ Error deteniendo la sesión de Spark: {e}")
        finally:
            _SPARK_CACHE = None
            print("🛑 Sesión local de Spark detenida correctamente. Recursos liberados.")
    else:
        print("ℹ️ No hay ninguna sesión local de Spark activa que detener.")
