from typing import List, MutableMapping, Optional, Dict, Any
from pathlib import Path
import yaml
import hashlib
from .field_mapper import FieldTranslationManager

# Singleton instance
_SHARED_MANAGER: Optional[FieldTranslationManager] = None


class FieldMapFactory:
    """
    Factory for creating field translation maps.
    Uses FieldTranslationManager backed by Global Field Repository.
    """

    @classmethod
    def create(cls, table_name: str, columns: List[str]) -> MutableMapping[str, str]:
        """
        Creates a field map dictionary for the given table and columns.

        Args:
            table_name: The name of the database table (context).
            columns: List of column names to translate.

        Returns:
            A MutableMapping (dict) where key=column_name, value=translated_name.
        """
        manager = cls._get_manager()
        mapping = {}

        for col in columns:
            # Generate ID via name only (type ignored in hashing per FieldTranslationManager logic)
            # We pass "unknown" as type since it's not used for ID generation
            fid = manager._generate_id(col, "unknown")
            field = manager.fields.get(fid)

            if field and field.target_column:
                mapping[col] = field.target_column
            else:
                mapping[col] = col

        return mapping

    @classmethod
    def _get_manager(cls) -> FieldTranslationManager:
        global _SHARED_MANAGER
        if _SHARED_MANAGER is None:
            _SHARED_MANAGER = FieldTranslationManager()

            # 1. Try to load from modular config files
            # Search order: conf/ -> generators/datacubes/
            root = Path.cwd()
            config_dirs = [root / "conf", root / "generators" / "datacubes"]
            config_files = ["discovery_params.yaml", "databases.yaml", "storage.yaml", "osmnx.yaml", "api.yaml"]
            repo_path = None

            for c_dir in config_dirs:
                if repo_path:
                    break
                for filename in config_files:
                    p = c_dir / filename
                    if p.exists():
                        try:
                            with open(p, "r") as f:
                                params = yaml.safe_load(f) or {}
                                # Check paths.repositories.global_field_repository_file
                                configured_path = (
                                    params.get("paths", {})
                                    .get("repositories", {})
                                    .get("global_field_repository_file")
                                )
                                if configured_path:
                                    repo_path = Path(configured_path)
                                    break
                        except Exception as e:
                            print(f"Warning: Failed to read {filename} in {c_dir} for field repo: {e}")

            # 2. Fallback to standard locations if not found
            if not repo_path:
                # Find project root by looking for pyproject.toml upwards
                root = Path.cwd()
                project_root = None
                for p in [root] + list(root.parents):
                    if (p / "pyproject.toml").exists():
                        project_root = p
                        break
                
                candidates = [
                    Path("test_prj/dataobjects/globals/global_field_repository.yaml"),
                    Path("solutions/dataobjects/globals/global_field_repository.yaml"),
                    Path("dataobjects/globals/global_field_repository.yaml"),
                    Path("solutions/conf/global_field_repository.yaml"),
                ]
                
                for c in candidates:
                    # Check relative to CWD first (default)
                    if c.exists():
                        repo_path = c
                        break
                    # Then check relative to project root if found
                    if project_root and (project_root / c).exists():
                        repo_path = project_root / c
                        break

            # 3. Load if found
            if repo_path and repo_path.exists():
                try:
                    with open(repo_path, "r") as f:
                        data = yaml.safe_load(f) or []
                        _SHARED_MANAGER.load_from_list(data)
                        # print(f"Loaded Field Repository from {repo_path}")
                except Exception as e:
                    print(
                        f"Warning: Failed to load Global Field Repository from {repo_path}: {e}"
                    )
            else:
                print(
                    f"Warning: Global Field Repository not found (checked params and defaults)"
                )

        return _SHARED_MANAGER

    @classmethod
    def create_metadata(cls, table_name: str, columns: List[str]) -> Dict[str, Any]:
        """
        Creates a dictionary of metadata for the given table and columns.

        Returns:
            Dict containing:
            - validation_schema
            - boolean_columns
            - numeric_int_columns
            - numeric_float_columns
            - date_fields
        """
        manager = cls._get_manager()
        if not manager:
            return {}

        metadata = {
            "validation_schema": {},
            "boolean_columns": [],
            "numeric_int_columns": [],
            "numeric_float_columns": [],
            "date_fields": [],
        }

        # Canonical Type Map (matches DfValidator normalization)
        type_map = {
            "string": "string[pyarrow]",
            "int64": "Int64[pyarrow]",
            "int32": "Int32[pyarrow]",
            "int16": "Int16[pyarrow]",
            "int8": "Int8[pyarrow]",
            "float64": "Float64[pyarrow]",
            "float32": "Float32[pyarrow]",
            "timestamp[ns]": "datetime64[ns, UTC]",
            "bool": "boolean[pyarrow]",
        }

        for col in columns:
            # 1. Generate ID (md5 of lowercase name)
            fid = hashlib.md5(col.lower().encode()).hexdigest()

            # 2. Lookup definition
            field_def = manager.fields.get(fid)

            if field_def:
                # Use target_column (alias) as key
                key_name = field_def.target_column or col

                # Determine Type
                ptype = field_def.type
                if "pyarrow" in field_def.mappings:
                    raw_type = field_def.mappings["pyarrow"]
                    ptype = type_map.get(raw_type, raw_type)

                # Populate Validation Schema
                metadata["validation_schema"][key_name] = ptype

                # Populate Lists
                if ptype == "boolean[pyarrow]":
                    metadata["boolean_columns"].append(key_name)
                elif ptype in (
                    "Int64[pyarrow]",
                    "Int32[pyarrow]",
                    "Int16[pyarrow]",
                    "Int8[pyarrow]",
                ):
                    metadata["numeric_int_columns"].append(key_name)
                elif ptype in ("Float64[pyarrow]", "Float32[pyarrow]"):
                    metadata["numeric_float_columns"].append(key_name)
                elif ptype.startswith("timestamp") or ptype == "datetime64[ns, UTC]":
                    metadata["date_fields"].append(key_name)

        return metadata
