import datetime
import os
import inspect
import sys
import time
import threading
import json
from contextlib import contextmanager

# Colors
class Colors:
    RESET = "\033[0m"
    RED = "\033[91m"
    GREEN = "\033[92m"
    YELLOW = "\033[93m"
    BLUE = "\033[94m"
    CYAN = "\033[96m"
    PURPLE = "\033[95m"
    MAGENTA = "\033[35m"
    GRAY = "\033[90m"
    LIGHT_GRAY = "\033[37m"

class LogLevel:
    TRACE = 0
    DEBUG = 1
    INFO = 2
    NOTICE = 3
    SUCCESS = 4
    WARN = 5
    ERROR = 6
    CRITICAL = 7

    _MAP = {
        "TRACE": TRACE,
        "DEBUG": DEBUG,
        "INFO": INFO,
        "NOTICE": NOTICE,
        "SUCCESS": SUCCESS,
        "WARN": WARN,
        "ERROR": ERROR,
        "CRITICAL": CRITICAL
    }

    @classmethod
    def from_string(cls, level: str) -> int:
        return cls._MAP.get(level.upper(), cls.INFO)

class KiwiLogger:
    def __init__(self):
        # Default configuration
        self.level = LogLevel.from_string(os.getenv("KIWILOG_LEVEL", "INFO"))
        self.log_to_file = os.getenv("KIWILOG_FILE_LOGGING", "true").lower() == "true"
        self.log_file = os.getenv("KIWILOG_LOG_FILE", os.path.join("logs", "bot.log"))
        self.show_timestamp = os.getenv("KIWILOG_SHOW_TIMESTAMP", "true").lower() == "true"
        self.show_filename = os.getenv("KIWILOG_SHOW_FILENAME", "true").lower() == "true"
        self.pretty_print = os.getenv("KIWILOG_PRETTY", "true").lower() == "true"
        self._loading_active = False

    def configure(self, level=None, log_to_file=None, log_file=None, show_timestamp=None, show_filename=None, pretty_print=None):
        if level is not None:
            if isinstance(level, str):
                self.level = LogLevel.from_string(level)
            else:
                self.level = level
        if log_to_file is not None:
            self.log_to_file = log_to_file
        if log_file is not None:
            self.log_file = log_file
            log_dir = os.path.dirname(self.log_file)
            if log_dir and not os.path.exists(log_dir):
                os.makedirs(log_dir)
        if show_timestamp is not None:
            self.show_timestamp = show_timestamp
        if show_filename is not None:
            self.show_filename = show_filename
        if pretty_print is not None:
            self.pretty_print = pretty_print

    def _format_message(self, message):
        if not isinstance(message, str) and self.pretty_print:
            try:
                return json.dumps(message, indent=2, ensure_ascii=False)
            except (TypeError, ValueError):
                return repr(message)
        return str(message)

    def _write_file(self, message: str):
        if not self.log_to_file:
            return
        
        log_dir = os.path.dirname(self.log_file)
        if log_dir and not os.path.exists(log_dir):
            os.makedirs(log_dir)

        timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        try:
            with open(self.log_file, "a", encoding="utf-8") as f:
                f.write(f"[{timestamp}] {message}\n")
        except Exception:
            pass

    def _log(self, color, level_name, message, level_value):
        if level_value < self.level:
            return

        message = self._format_message(message)

        # If loading is active, clear the line first
        if self._loading_active:
            sys.stdout.write("\r\033[K")

        frame = inspect.stack()[2]
        filename = os.path.basename(frame.filename)
        
        output = f"{color}"
        if self.show_timestamp:
            time_str = datetime.datetime.now().strftime("%H:%M:%S")
            output += f"[{time_str}] "
        
        output += f"[{level_name}] "
        
        if self.show_filename:
            output += f"[{filename}] "
        
        output += f"{message}{Colors.RESET}"

        print(output)
        self._write_file(f"[{level_name}] [{filename}] {message}")

# Global instance
_default_logger = KiwiLogger()

def configure(**kwargs):
    _default_logger.configure(**kwargs)

# Logging functions
def trace(message):
    _default_logger._log(Colors.GRAY, "TRACE", message, LogLevel.TRACE)

def debug(message):   
    _default_logger._log(Colors.CYAN, "DEBUG", message, LogLevel.DEBUG)

def info(message):    
    _default_logger._log(Colors.BLUE, "INFO", message, LogLevel.INFO)

def notice(message):
    _default_logger._log(Colors.MAGENTA, "NOTICE", message, LogLevel.NOTICE)

def success(message): 
    _default_logger._log(Colors.GREEN, "SUCCESS", message, LogLevel.SUCCESS)

def waiting(message):
    _default_logger._log(Colors.YELLOW, "WAITING", message, LogLevel.WARN)

def warn(message):    
    _default_logger._log(Colors.YELLOW, "WARN", message, LogLevel.WARN)

def error(message):   
    _default_logger._log(Colors.RED, "ERROR", message, LogLevel.ERROR)

def critical(message):
    _default_logger._log(Colors.PURPLE, "CRITICAL", message, LogLevel.CRITICAL)

@contextmanager
def loading(message):
    _default_logger._loading_active = True
    stop_event = threading.Event()
    
    def animate():
        chars = [".  ", ".. ", "...", ".. "]
        idx = 0
        while not stop_event.is_set():
            time_str = datetime.datetime.now().strftime("%H:%M:%S") if _default_logger.show_timestamp else ""
            ts_part = f"[{time_str}] " if time_str else ""
            sys.stdout.write(f"\r{Colors.CYAN}{ts_part}[LOADING] {message}{chars[idx % len(chars)]}{Colors.RESET}")
            sys.stdout.flush()
            idx += 1
            time.sleep(0.4)

    t = threading.Thread(target=animate, daemon=True)
    t.start()
    try:
        yield
    finally:
        stop_event.set()
        t.join()
        sys.stdout.write("\r\033[K") # Clear the loading line
        sys.stdout.flush()
        _default_logger._loading_active = False

class ProgressBar:
    def __init__(self, total, prefix='', suffix='', decimals=1, length=40, fill='█', print_end="\r"):
        self.total = total
        self.prefix = prefix
        self.suffix = suffix
        self.decimals = decimals
        self.length = length
        self.fill = fill
        self.print_end = print_end
        self.iteration = 0

    def update(self, iteration=None):
        if iteration is not None:
            self.iteration = iteration
        else:
            self.iteration += 1
        
        percent = ("{0:." + str(self.decimals) + "f}").format(100 * (self.iteration / float(self.total)))
        filled_length = int(self.length * self.iteration // self.total)
        bar = self.fill * filled_length + '-' * (self.length - filled_length)
        
        color = Colors.GREEN if self.iteration == self.total else Colors.CYAN
        
        sys.stdout.write(f'\r{color}{self.prefix} |{bar}| {percent}% {self.suffix}{Colors.RESET}')
        sys.stdout.flush()
        
        if self.iteration == self.total:
            print()

# Helper
def format_exception(e):
    return f"{type(e).__name__}: {e}"