from __future__ import annotations
import os
import yaml
from pathlib import Path
from typing import Optional, Dict, Any, List
from rich.console import Console

console = Console()

class ProjectService:
    """
    Centralized service for project-level concerns: root detection, configuration loading,
    and environment initialization.
    """

    CONFIG_FILES = ["discovery_params.yaml", "databases.yaml", "storage.yaml", "osmnx.yaml", "api.yaml", "apps.yaml"]

    @staticmethod
    def detect_project_root(start_path: Optional[Path] = None) -> Path:
        """
        Heuristic to find the project root.
        Looks for 'conf/', 'generators/', or '.env' starting from start_path.
        """
        curr = Path(start_path or os.getcwd()).resolve()
        
        # Check if we are already in a known root or immediate child
        for _ in range(5):  # Limit recursion depth
            if (curr / "conf").exists() or (curr / "generators").exists() or (curr / ".env").exists():
                return curr
            if curr.parent == curr:
                break
            curr = curr.parent
            
        return Path(os.getcwd()).resolve()

    @staticmethod
    def load_modular_config(project_root: Path, config_dir: Optional[Path] = None) -> Dict[str, Any]:
        """
        Loads and merges all modular YAML configuration files.
        Prioritizes 'conf/' over legacy 'generators/datacubes/'.
        """
        merged_config = {}
        
        # Search locations in order of priority
        search_dirs = []
        if config_dir:
            search_dirs.append(config_dir)
        
        search_dirs.append(project_root / "conf")
        search_dirs.append(project_root / "generators" / "datacubes")

        found_files = set()
        for s_dir in search_dirs:
            if not s_dir.exists():
                continue
                
            for filename in ProjectService.CONFIG_FILES:
                if filename in found_files:
                    continue
                    
                path = s_dir / filename
                if path.exists():
                    try:
                        with open(path, "r") as f:
                            data = yaml.safe_load(f) or {}
                            merged_config.update(data)
                            found_files.add(filename)
                    except Exception as e:
                        console.print(f"[yellow]Warning: Failed to load {path}: {e}[/yellow]")
        
        return merged_config

    @staticmethod
    def setup_environment(project_root: Path, env_file: Optional[Path] = None) -> Path:
        """
        Standardized environment loading.
        1. CLI provided env_file
        2. .env in project root
        3. .env.linux in project root (legacy fallback)
        """
        if env_file:
            target = env_file if env_file.is_absolute() else project_root / env_file
        else:
            # Defaults
            candidates = [
                project_root / ".env",
                project_root / ".env.linux",
                project_root / ".env.local",
            ]
            target = candidates[0]
            for c in candidates:
                if c.exists():
                    target = c
                    break

        if target.exists():
            # Use manual parsing as currently done in the toolkit to maintain behavior
            # (In the future, consider using python-dotenv if dependency is added)
            with open(target, "r") as f:
                for line in f:
                    line = line.strip()
                    if not line or line.startswith("#"):
                        continue
                    if "=" in line:
                        key, value = line.split("=", 1)
                        os.environ[key.strip()] = value.strip().strip("'").strip('"')
        
        # Add project root to sys.path to allow relative imports (e.g. conf.credentials)
        root_str = str(project_root.resolve())
        import sys
        if root_str not in sys.path:
             sys.path.insert(0, root_str)

        return target

    @staticmethod
    def ensure_directories_exist(
        params: Dict[str, Any], logger: Optional[Any] = None
    ) -> None:
        """
        Ensures that directories specified in configuration parameters exist.
        Handles 'folder_prefix' and 'fields_module_root' (relative to prefix or absolute).
        """
        if not params:
            return

        # 1. target_datacubes_folder (was folder_prefix)
        folder_prefix = params.get("paths", {}).get("target", {}).get("datacubes_dir")
        if not folder_prefix:
            # Legacy fallback
            folder_prefix = params.get("target_datacubes_folder") or params.get(
                "folder_prefix"
            )

        if folder_prefix:
            prefix_path = Path(folder_prefix)
            if not prefix_path.exists():
                msg = f"Creating missing folder_prefix: {prefix_path}"
                if logger:
                    if hasattr(logger, "info"):
                        logger.info(msg)
                    else:
                        logger(msg)
                else:
                    print(msg)
                prefix_path.mkdir(parents=True, exist_ok=True)

            # Create __init__.py if missing
            if not (prefix_path / "__init__.py").exists():
                (prefix_path / "__init__.py").touch()

            # 2. Iterate databases to create domain-specific paths
            databases = params.get("databases", [])
            fields_root = params.get("generation", {}).get("fields_subpackage")
            if not fields_root:
                fields_root = params.get("fields_subpackage") or params.get(
                    "fields_module_root"
                )

            for db_config in databases:
                db_domain = db_config.get("db_domain")
                if not db_domain:
                    continue

                # A. Ensure domain directory exists
                domain_path = prefix_path / db_domain
                if not domain_path.exists():
                    msg = f"Creating missing domain directory: {domain_path}"
                    if logger:
                        if hasattr(logger, "info"):
                            logger.info(msg)
                        else:
                            logger(msg)
                    else:
                        print(msg)
                    domain_path.mkdir(parents=True, exist_ok=True)

                if not (domain_path / "__init__.py").exists():
                    (domain_path / "__init__.py").touch()

                # B. Ensure fields directory exists within domain
                if fields_root:
                    full_fields_path = domain_path / fields_root
                    if not full_fields_path.exists():
                        msg = f"Creating missing fields directory: {full_fields_path}"
                        if logger:
                            if hasattr(logger, "info"):
                                logger.info(msg)
                            else:
                                logger(msg)
                        else:
                            print(msg)
                        full_fields_path.mkdir(parents=True, exist_ok=True)

                    if not (full_fields_path / "__init__.py").exists():
                        (full_fields_path / "__init__.py").touch()
