import logging
import os
from pathlib import Path
import sys

from dotenv import dotenv_values

from cpbox.app.tools import tibaselog
from cpbox.tool import datatypes
from cpbox.tool import cls
from cpbox.tool import file

logger = logging.getLogger(__name__)


def _detect_start_dir():
    # 启动目录判定规则：
    # 1) 正常脚本方式启动（python xxx.py）时，优先使用入口脚本所在目录，后续向上查找 .env 会更符合项目预期。
    # 2) 交互式场景（如 REPL / notebook / python -c）通常没有 __main__.__file__，回退到当前工作目录 cwd。
    if hasattr(sys.modules['__main__'], '__file__'):
        return Path(sys.argv[0]).resolve().parent
    return Path.cwd()


def detect_framework_root_dir(start_dir=None):
    if start_dir is None:
        start_dir = _detect_start_dir()
    else:
        start_dir = Path(start_dir).resolve()
    while True:
        if (start_dir / '.env').exists():
            return start_dir
        if start_dir == start_dir.parent:
            return None
        start_dir = start_dir.parent


def load_env_config_by_priority(framework_root_dir=None):
    if framework_root_dir is None:
        return EnvConfig(dict(os.environ))
    env_dict = dotenv_values(framework_root_dir / '.env')
    if (framework_root_dir / '.env.local').exists():
        env_local_dict = dotenv_values(framework_root_dir / '.env.local')
        env_dict.update(env_local_dict)
    for key, value in env_dict.items():
        if value is None:
            continue
        if key in os.environ:
            continue
        os.environ[key] = value
    return EnvConfig(dict(os.environ))


class EnvConfig(dict):

    def __init__(self, data):
        dict.__init__(self, data or {})

    def _to_type(self, raw, item_type, default, strict):
        return datatypes.convert_scalar_value(raw, item_type, default=default, strict=strict)

    def get_bool(self, key, default=False):
        return self._to_type(self.get(key, None), bool, default, strict=False)

    def get_int(self, key, default=0, strict=True):
        return self._to_type(self.get(key, None), int, default, strict)

    def get_float(self, key, default=0.0, strict=True):
        return self._to_type(self.get(key, None), float, default, strict)

    def get_list(self, key, item_type=str, sep=',', default=None):
        raw = self.get(key, None)
        if raw is None:
            return default if default is not None else []
        items = str(raw).split(sep)
        items = [item.strip() for item in items]
        items = [item for item in items if item]
        return [self._to_type(item, item_type, default, strict=True) for item in items]


def _normalize_app_env(env_config):
    app_env = env_config.get('APP_ENV') or env_config.get('ENV') or 'prod'
    app_env = str(app_env).lower()
    if app_env not in ('dev', 'test', 'pre', 'prod'):
        return 'prod'
    return app_env


class GContext(cls.Singleton):

    def __init__(self):
        super().__init__()

    def _init_once(self):
        self.mode = 'basic'
        self.framework_root_dir = None
        self.output_dir = None
        self.env_config = EnvConfig(dict())
        self.is_dev_mode = False
        self._has_init_done = False

    def try_enable_framework_mode(self):
        if self._has_init_done:
            return
        start_dir = _detect_start_dir()
        self.framework_root_dir = detect_framework_root_dir(start_dir)
        self.mode = 'framework' if self.framework_root_dir is not None else 'basic'
        self.output_dir = None
        self.env_config = load_env_config_by_priority(self.framework_root_dir)
        app_env = _normalize_app_env(self.env_config)
        self.is_dev_mode = app_env in ('dev', 'test')
        self._init_logger()
        self._has_init_done = True

    def _init_logger(self):
        tibaselog.log_manager.init_from_env(self.env_config)
        if self.mode != 'framework':
            return
        self.output_dir = self.framework_root_dir / 'output'
        file.ensure_dir(self.output_dir)
        logs_dir = self.output_dir / 'logs'
        app_name = self.env_config.get('APP_NAME', 'tiapp')
        tibaselog.log_manager.add_file_logger(app_name, logs_dir)


gcontext = GContext()
