import logging 
import warnings

import pandas as pd
import streamlit as st
import traceback
import types
import sys

import functools

from streamlit.runtime.scriptrunner import get_script_run_ctx


DEFAULT_LOG_FORMAT = '[%(asctime)s] {%(filename)s} %(levelname)s: %(message)s'
LOGGERS_TO_DEFINE = {
   'botocore': logging.INFO,
   'boto3': logging.INFO,
   'googleapiclient': logging.WARNING,
   '_client': logging.INFO,
   'pandas': logging.DEBUG
}

custom_box = """
<div style="
    background-color: {0};
    padding: 1rem;
    margin: 0.75rem 0; 
    border-radius: 0.5rem;
    border-left: 0.4rem solid {0};
    font-family: 'Segoe UI', sans-serif;
">
    <span style="font-size: 1.5rem;">🕙</span>
    {1}: The process {2} required {3} seconds
</div>
"""


def control_time(last_update, now_time, process_name, rgba_colors = 'rgba(187, 106, 8, 0.8)'):
    
    time_required = (now_time - last_update).total_seconds()
    
    st.markdown(custom_box.format(rgba_colors, now_time, process_name, time_required), unsafe_allow_html=True)
    
    return now_time


def set_logger_config(
    logger_level = logging.DEBUG,
    log_format = DEFAULT_LOG_FORMAT,
    date_format = None,
    loggers_to_define = LOGGERS_TO_DEFINE
):
    """
    We define in this function the setup that we like in order to standarize our logs and
    define what logs what we want to read from certain libraries
    """

    logging.basicConfig(format = DEFAULT_LOG_FORMAT, datefmt = date_format, level = logging.INFO)
    
    warnings.simplefilter(action='ignore', category=pd.errors.SettingWithCopyWarning)

    # Define logger level for those libraries from loggers_to_silence
    for logger_name, logger_level in loggers_to_define.items():
        logging.getLogger(logger_name).setLevel(logger_level)


def log_error_with_variables(
        exception: Exception, 
        context_vars: dict, 
        error_type: str, 
        file: str,
        line: int,
        code_context: str,
        traceback_str: str,
        ):

    variables_info = []

    for var_name, var_value in context_vars.items():
        if var_name.startswith("_") or callable(var_value) or isinstance(var_value, types.ModuleType) or isinstance(var_value, bytes):
            continue

        try:
            var_type = type(var_value).__name__
            var_size = len(var_value) if hasattr(var_value, '__len__') else "N/A"

            if isinstance(var_value, pd.DataFrame):
                preview = var_value.head(3).to_string()
                variables_info.append(
                    f"Name: {var_name}, Type: {var_type}, Size: {var_size}\nPreview:\n{preview}"
                )
            else:
                variables_info.append(
                    f"Name: {var_name}, Type: {var_type}, Size: {var_size}, Value: {repr(var_value)}"
                )
        except Exception as e:
            variables_info.append(f"Name: {var_name}, Error accessing variable info: {e}")

    error_message = "".join(traceback.format_exception(type(exception), exception, exception.__traceback__))

    full_log = "Variables in use:\n" + "\n".join(variables_info) + '\n\n' + error_message + '\n\n' + f'Error Type: {error_type}\nError Message: {error_message}\nFile: {file}\nLine: {line}\nCode Context: {code_context}\nTraceback:\n{traceback_str}'
    
    return full_log


def capture_errors(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            exc_type, exc_value, exc_traceback = sys.exc_info()
            tb = traceback.extract_tb(exc_traceback)

            last_call = tb[-1] # last frame where the error happened

            context = {}
            context.update(locals())  # incluye args, kwargs, e
            context.update({"__locals__": func.__code__.co_varnames})

            return {
                "error": e,
                "context": context,
                "error_type": exc_type.__name__,
                "file": last_call.filename,
                "line": last_call.lineno,
                "code_context": last_call.line,
                "traceback": "".join(traceback.format_exception(exc_type, exc_value, exc_traceback)),
                
            }
    return wrapper


class StreamlitHandler(logging.Handler):
    def __init__(self, placeholder):
        super().__init__()
        self.placeholder = placeholder
        self.logs = []

    def emit(self, record):
        log_entry = self.format(record)
        self.logs.append(log_entry)
        if get_script_run_ctx() is not None:
            try:
                self._render()
            except Exception:
                # Evita que un fallo de renderizado rompa todo
                pass

    def clear(self):
        self.logs = []
        self._render()

    def _render(self):

        if get_script_run_ctx() is None:
            return  # Evita errores fuera de Streamlit
        
        logs_formatted = "\n".join(self.logs) if self.logs else ""
        self.placeholder.markdown(
            f"""
            <div style="
                background-color: #272827;
                color: #e9e9e9;
                font-family: monospace;
                font-size: 15px;
                padding: 25px;
                margin: 20px;
                border-radius: 15px;
                height: 350px;
                overflow-y: auto;
                white-space: pre-wrap;
            ">{logs_formatted.strip()}</div>
                    """,
                    unsafe_allow_html=True
        )
  
def setup_logger(streamlit_handler=None):
    root_logger = logging.getLogger()
    root_logger.setLevel(logging.INFO)

    # 🔥 Limpia todos los StreamlitHandler previos
    for handler in list(root_logger.handlers):
        if isinstance(handler, StreamlitHandler):
            root_logger.removeHandler(handler)

    # 🧱 Agrega el nuevo handler
    if streamlit_handler:
        root_logger.addHandler(streamlit_handler)

    return root_logger