from typing import Optional, Tuple
"""
Context detection and management for org-aware logging.
"""

import re
from datetime import datetime
from pathlib import Path


class LogContext:
    """
    Manages logging context including org, run ID, stage, and file paths.
    """

    def __init__(
        self,
        org: Optional[str] = None,
        run_id: Optional[str] = None,
        stage: Optional[str] = None,
        component: Optional[str] = None,
    ):
        self.org = org
        self.run_id = run_id
        self.stage = stage
        self.component = component

        # Determine base directory for logs
        self.base_dir = self._find_base_directory()

        # Setup directory structure
        if self.org and self.run_id:
            # Org-specific run logs
            self.run_dir = self.base_dir / "logs" / self.org / "runs" / self.run_id
        else:
            # Global logs
            self.run_dir = self.base_dir / "logs"

        # Log file paths
        self.user_log = self.run_dir / "user.log"
        self.debug_log = self.run_dir / "debug.log"
        self.performance_log = self.run_dir / "performance.log"
        self.trace_log = self.run_dir / "trace.log"

        # Org-level log paths
        if self.org:
            org_dir = self.base_dir / "logs" / self.org
            self.org_log = org_dir / "org.log"
        else:
            self.org_log = None

        # Global log paths
        logs_dir = self.base_dir / "logs"
        self.system_log = logs_dir / "system.log"
        self.errors_log = logs_dir / "errors.log"

    def _find_base_directory(self) -> Path:
        """Find the base directory for the workspace."""
        # Start from current working directory and look for workspace indicators
        current = Path.cwd()

        # Look for workspace indicators
        workspace_indicators = [
            "cloudzero-demo-billing",
            ".pipeline.yaml",
            "orgs",
            "pyproject.toml",
        ]

        # Walk up the directory tree
        for parent in [current] + list(current.parents):
            for indicator in workspace_indicators:
                if (parent / indicator).exists():
                    if indicator == "cloudzero-demo-billing":
                        return parent / "cloudzero-demo-billing"
                    else:
                        return parent

        # Fallback to current directory
        return current

    def ensure_directories(self):
        """Ensure all required log directories exist."""
        directories = [self.run_dir, self.base_dir / "logs"]

        for directory in directories:
            directory.mkdir(parents=True, exist_ok=True)

    @classmethod
    def from_run_dir(cls, run_dir: str, stage: Optional[str] = None) -> "LogContext":
        """
        Create LogContext directly from a pipeline run directory path.

        This method eliminates the need for run ID generation by extracting
        context directly from the pipeline's run directory structure.

        Args:
            run_dir: Path to pipeline run directory (e.g., "orgs/demo_test/billing/runs/2025-07-06_151559")
            stage: Optional stage override

        Returns:
            LogContext with extracted org and run_id

        Example:
            >>> context = LogContext.from_run_dir("orgs/demo_test/billing/runs/2025-07-06_151559", "generate")
            >>> context.org
            'demo_test'
            >>> context.run_id
            '2025-07-06_151559'
        """
        run_path = Path(run_dir)
        path_parts = run_path.parts

        # Extract org from path: orgs/{org_name}/billing/runs/{run_id}
        org = None
        run_id = None

        try:
            if "orgs" in path_parts:
                org_index = path_parts.index("orgs")
                if org_index + 1 < len(path_parts):
                    org = path_parts[org_index + 1]

            if "runs" in path_parts:
                runs_index = path_parts.index("runs")
                if runs_index + 1 < len(path_parts):
                    run_id = path_parts[runs_index + 1]
        except (ValueError, IndexError):
            # Fallback: use the last directory as run_id if it looks like a timestamp
            import re

            timestamp_pattern = re.compile(r"\d{4}-\d{2}-\d{2}_\d{6}")
            for part in reversed(path_parts):
                if timestamp_pattern.match(part):
                    run_id = part
                    break

        # Detect component from call stack
        component = cls._detect_component()

        return cls(org=org, run_id=run_id, stage=stage, component=component)

    @classmethod
    def detect_current_context(
        cls, org: Optional[str] = None, run_id: Optional[str] = None, stage: Optional[str] = None
    ) -> "LogContext":
        """
        Detect current context from environment and working directory.

        Args:
            org: Explicit org name (overrides detection)
            run_id: Explicit run ID (overrides detection)
            stage: Explicit stage (overrides detection)

        Returns:
            LogContext with detected or provided values
        """
        # Resolve context parameters using detection or provided values
        resolved_org, resolved_run_id, resolved_stage, resolved_component = (
            cls._resolve_context_parameters(org, run_id, stage)
        )

        return cls(
            org=resolved_org,
            run_id=resolved_run_id,
            stage=resolved_stage,
            component=resolved_component,
        )

    @staticmethod
    def _detect_org() -> Optional[str]:
        """Detect org name from CLI args or working directory."""
        # Check CLI arguments
        import sys

        if len(sys.argv) > 2:
            # Look for org name in CLI args like: czbillgen pipeline run demo_test
            for i, arg in enumerate(sys.argv):
                if arg in ["run", "validate"] and i + 1 < len(sys.argv):
                    potential_org = sys.argv[i + 1]
                    if not potential_org.startswith("-"):
                        return potential_org

        # Check working directory path
        cwd = Path.cwd()
        path_parts = cwd.parts

        # Look for 'orgs' in path and get the next part
        try:
            org_index = path_parts.index("orgs")
            if org_index + 1 < len(path_parts):
                return path_parts[org_index + 1]
        except ValueError:
            pass

        # Check for org in path patterns like: .../orgs/myorg/...
        path_str = str(cwd)
        if "/orgs/" in path_str:
            org_part = path_str.split("/orgs/")[1].split("/")[0]
            if org_part:
                return org_part

        return None

    @staticmethod
    def _detect_run_id() -> Optional[str]:
        """Detect run ID from working directory or generate new one."""
        cwd = Path.cwd()
        path_parts = cwd.parts

        # Look for timestamp pattern in path like: 2025-07-01_175411
        timestamp_pattern = re.compile(r"\d{4}-\d{2}-\d{2}_\d{6}")

        for part in reversed(path_parts):
            if timestamp_pattern.match(part):
                return part

        # Check if we're in a runs directory
        if "runs" in path_parts:
            try:
                runs_index = path_parts.index("runs")
                if runs_index + 1 < len(path_parts):
                    return path_parts[runs_index + 1]
            except ValueError:
                pass

        # Generate new run ID if we can't detect one
        return datetime.now().strftime("%Y-%m-%d_%H%M%S")

    @staticmethod
    def _detect_stage() -> Optional[str]:
        """Detect current stage from call stack."""
        import inspect

        # Look for stage names in the call stack
        stage_names = ["generate", "k8s", "tax", "metadata", "validate", "upload"]

        for frame_info in inspect.stack():
            filename = frame_info.filename
            for stage in stage_names:
                if f"/stages/{stage}/" in filename or filename.endswith(f"{stage}.py"):
                    return stage

        return None

    @staticmethod
    def _detect_component() -> Optional[str]:
        """Detect component name from call stack."""
        import inspect

        # Get the calling module name
        for frame_info in inspect.stack()[2:4]:  # Skip our own frames
            module = inspect.getmodule(frame_info.frame)
            if module and hasattr(module, "__name__"):
                module_name = module.__name__
                # Extract meaningful component name
                if "cz_billing_generator" in module_name:
                    parts = module_name.split(".")
                    if len(parts) >= 3:
                        return parts[-1]  # Last part like 'generate', 'validator', etc.

        return None

    @classmethod
    def _resolve_context_parameters(
        cls, org: Optional[str], run_id: Optional[str], stage: Optional[str]
    ) -> Tuple[Optional[str], Optional[str], Optional[str], Optional[str]]:
        """Resolve context parameters using detection or provided values."""
        # Detect org from CLI args or working directory
        if not org:
            org = cls._detect_org()

        # Detect run ID from working directory or generate new one
        if not run_id:
            run_id = cls._detect_run_id()

        # Detect stage from call stack or environment
        if not stage:
            stage = cls._detect_stage()

        # Detect component from call stack
        component = cls._detect_component()

        return org, run_id, stage, component
