"""Execution context for MakePython commands."""

from __future__ import annotations

import logging
from dataclasses import dataclass, field
from pathlib import Path
from typing import TYPE_CHECKING, Dict, Optional

from pytola.core.logger import get_logger

from .._config import MakePythonConfig
from .cache import ProjectCache

if TYPE_CHECKING:
    from pytola.dev.pypack.models.project import Project

    from ..interfaces.types import BuildTool


@dataclass
class ExecutionContext:
    """Execution context containing all necessary information for command execution."""

    cwd: Path = field(default_factory=Path.cwd)
    config: MakePythonConfig = field(default_factory=MakePythonConfig)
    logger: type(logger) = field(default_factory=get_logger)
    cache: ProjectCache = field(default_factory=ProjectCache)

    # Project-specific information (will be populated during execution)
    project_info: Optional[Project] = None
    project_root: Optional[Path] = None
    build_tool: Optional[BuildTool] = None
    env_vars: Dict[str, str] = field(default_factory=dict)

    def __post_init__(self):
        """Initialize the execution context after dataclass construction."""
        # Set up logging level based on config
        if self.config.verbose_output:
            self.logger.setLevel(logging.DEBUG)
        else:
            self.logger.setLevel(logging.INFO)

        # Detect project root and project info
        self._detect_project()

    def _detect_project(self) -> None:
        """Detect project information and set up project-specific attributes."""
        # Look for pyproject.toml in current directory or parent directories
        current_path = self.cwd
        while current_path != current_path.parent:
            pyproject_path = current_path / "pyproject.toml"
            if pyproject_path.exists():
                self.project_root = current_path

                # Load project info using cache
                cached_project = self.cache.get(pyproject_path)
                if cached_project:
                    self.project_info = cached_project
                    self.logger.debug(f"Using cached project info: {cached_project.name}")
                else:
                    try:
                        from pytola.dev.pypack.models.project import Project

                        project = Project.from_toml_file(pyproject_path)
                        if project and project.name:
                            self.project_info = project
                            self.cache.put(pyproject_path, project)
                            self.logger.debug(f"Cached project info: {project.name}")
                    except Exception as e:
                        # Log at debug level since we have a fallback mechanism
                        # This avoids confusing users with warnings when build still works
                        self.logger.debug(f"Using fallback: Failed to parse full project info ({type(e).__name__})")

                break
            current_path = current_path.parent

    def get_project_info(self) -> Optional[Project]:
        """Get project information, attempting to load if not already loaded.

        Returns
        -------
            Project information if available, None otherwise
        """
        if self.project_info is None:
            self._detect_project()
        return self.project_info

    def get_build_tool(self) -> Optional[BuildTool]:
        """Get the detected build tool for the project.

        Returns
        -------
            Build tool if detected, None otherwise
        """
        if self.build_tool is None:
            from ..interfaces.types import BuildTool

            # Try to get build tool from parsed project info first
            if self.project_info:
                backend = self.project_info.build_backend
                if backend.startswith("poetry"):
                    self.build_tool = BuildTool.POETRY
                elif backend.startswith("hatch"):
                    self.build_tool = BuildTool.HATCH
                else:
                    self.build_tool = BuildTool.UV
            else:
                # Fallback: read build-backend directly from pyproject.toml
                backend = self._read_build_backend_from_pyproject()
                if backend:
                    if backend.startswith("poetry"):
                        self.build_tool = BuildTool.POETRY
                    elif backend.startswith("hatch"):
                        self.build_tool = BuildTool.HATCH
                    else:
                        self.build_tool = BuildTool.UV
                else:
                    # Default to UV if no build-backend found
                    self.build_tool = BuildTool.UV

        return self.build_tool

    def _read_build_backend_from_pyproject(self) -> Optional[str]:
        """Read build-backend field directly from pyproject.toml file.

        Returns
        -------
            Build backend string if found, None otherwise
        """
        if self.project_root is None:
            return None

        pyproject_path = self.project_root / "pyproject.toml"
        if not pyproject_path.exists():
            return None

        try:
            import tomllib
        except ImportError:
            import tomli as tomllib  # type: ignore

        try:
            with open(pyproject_path, "rb") as f:
                data = tomllib.load(f)
                return data.get("build-system", {}).get("build-backend")
        except Exception:
            self.logger.debug(f"Failed to read build-backend from {pyproject_path}")
            return None

    def update_env_vars(self, new_vars: Dict[str, str]) -> None:
        """Update environment variables for this context.

        Args:
            new_vars: Dictionary of environment variables to add/update
        """
        self.env_vars.update(new_vars)

    def get_full_env(self) -> Dict[str, str]:
        """Get the complete environment dictionary combining system env and context vars.

        Returns
        -------
            Complete environment dictionary
        """
        import os

        full_env = os.environ.copy()
        full_env.update(self.env_vars)
        return full_env


def create_default_context(cwd: Optional[Path] = None) -> ExecutionContext:
    """Create a default execution context.

    Args:
        cwd: Working directory for the context (defaults to current directory)

    Returns
    -------
        ExecutionContext instance
    """
    if cwd is None:
        cwd = Path.cwd()
    return ExecutionContext(cwd=cwd)
