import inspect
import logging
import os
from enum import Enum
from typing import List, Optional

logger = logging.getLogger("backend.execution")


def find_relative_path(caller_path: str, relative_path: str) -> Optional[str]:
    caller_dir = os.path.dirname(caller_path)
    relative_components = relative_path.split("/")
    old_caller_dir = None
    while caller_dir and caller_dir != old_caller_dir:
        candidate = os.path.join(caller_dir, *relative_components)
        if os.path.exists(candidate):
            return candidate
        old_caller_dir = caller_dir
        caller_dir = os.path.dirname(caller_dir)
    return None


class ExecutionContext(str, Enum):
    LOCAL = "LOCAL"
    DATAIKU_DSS = "DATAIKU_DSS"
    DATAIKU_DSS_CODE_STUDIO = "DATAIKU_DSS_CODE_STUDIO"


class Execution:
    dss_env_var = "DIP_HOME"
    dss_code_studio_env_var = "DKU_CODE_STUDIO_BROWSER_PATH"
    dss_current_project_env_var = "DKU_CURRENT_PROJECT_KEY"
    dss_code_studio_project_lib_env_var = "DKU_PROJECT_LIB_VERSIONED_LOCATION"
    dss_python_path_env_var = "PYTHONPATH"

    def __init__(self, relative_path: str, prefix: Optional[str] = None):
        self.prefix = prefix
        self.__context = self.__find_context()
        self.__dss_current_project = self.__get_dss_current_project()
        self.relative_path = relative_path.lstrip(" /").rstrip(" /")
        self.__exec_path = self.__get_execution_main_path()
        if not self.__verify_exec_path():
            if self.__context == ExecutionContext.DATAIKU_DSS:
                raise ValueError("Path for web application folder is not found")
            logger.warning("Path for web application folder is not found")
        logger.info(f"Web application execution context initialized with path {self.__exec_path}")

    @property
    def exec_path(self):
        return self.__exec_path

    @property
    def context(self):
        return self.__context

    @property
    def dss_current_project(self):
        return self.__dss_current_project

    def __find_context(self) -> ExecutionContext:
        if self.dss_env_var in os.environ:
            if self.dss_code_studio_env_var in os.environ:
                return ExecutionContext.DATAIKU_DSS_CODE_STUDIO
            return ExecutionContext.DATAIKU_DSS
        return ExecutionContext.LOCAL

    def __get_dss_current_project(self) -> Optional[str]:
        if self.context != ExecutionContext.LOCAL:
            return os.environ.get(self.dss_current_project_env_var)
        return None

    def __get_root_paths(self) -> List[str]:
        path_list = []
        if self.context == ExecutionContext.DATAIKU_DSS:
            paths = os.environ.get(self.dss_python_path_env_var)
            if paths:
                paths_splitted = paths.split(":")
                # DSS 14+ plugin code env: look for python-lib in plugin path
                for path in paths_splitted:
                    if path.endswith("python-lib"):
                        plugin_root = os.path.dirname(path)
                        path_list.append(plugin_root)
        return path_list

    def __get_execution_main_path(self):
        # DSS Code Studio: we don't handle it
        if self.context == ExecutionContext.DATAIKU_DSS_CODE_STUDIO:
            return None
        # DSS: look for managed folder or plugin root
        elif self.context == ExecutionContext.DATAIKU_DSS:
            root_paths = self.__get_root_paths()
            for root_path in root_paths:
                candidate = os.path.join(root_path, self.relative_path)
                if os.path.exists(candidate):
                    return candidate
            # Fallback: traverse up from current file
            caller_file = inspect.stack()[1].filename
            candidate = find_relative_path(caller_file, self.relative_path)
            if candidate:
                return candidate
            raise ValueError(f"Could not resolve path for {self.relative_path} in DSS context")
        else:
            # Local: traverse up from caller
            caller_file = inspect.stack()[1].filename
            candidate = find_relative_path(caller_file, self.relative_path)
            if candidate:
                return candidate
        return None

    def __verify_exec_path(self) -> bool:
        try:
            if self.exec_path and os.path.exists(self.exec_path):
                return True
            return False
        except Exception as e:
            raise e from None
