"""Service for worktree operations (UI-agnostic)."""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

from portal.core.domain.models import (
    HookStage,
    PortalConfig,
    Worktree,
    WorktreeListResult,
    WorktreeResult,
    WorktreeSafetyResult,
)
from portal.core.domain.value_objects import BranchName
from portal.core.events import WorktreeEvents

if TYPE_CHECKING:
    from portal.core.events import EventBus
    from portal.infrastructure.git import GitRepository
    from portal.shared.protocols.service_protocols import (
        ColorServiceProtocol,
        ConfigServiceProtocol,
        HookServiceProtocol,
    )


class WorktreeService:
    """Service for worktree operations (UI-agnostic)."""

    def __init__(
        self,
        git_repository: GitRepository,
        hook_service: HookServiceProtocol,
        color_service: ColorServiceProtocol,
        event_bus: EventBus,
        config: PortalConfig,
        config_service: ConfigServiceProtocol | None = None,
    ) -> None:
        self.git_repository = git_repository
        self.hook_service = hook_service
        self.color_service = color_service
        self.event_bus = event_bus
        self.config = config
        self.config_service = config_service

    def _validate_branch_name(self, branch_name: str) -> WorktreeResult | None:
        """Validate branch name using value object."""
        try:
            if not branch_name:
                return WorktreeResult.create_failure("Branch name is required")
            if len(branch_name) > 255:
                return WorktreeResult.create_failure("Branch name too long (max 255 characters)")
            BranchName(value=branch_name)
            return None
        except ValueError as e:
            # Extract user-friendly message from validation error
            error_str = str(e)
            if "at least 1 character" in error_str:
                return WorktreeResult.create_failure("Branch name is required")
            elif "at most 255 characters" in error_str:
                return WorktreeResult.create_failure("Branch name too long (max 255 characters)")
            return WorktreeResult.create_failure(f"Invalid branch name: {e}")

    def _safe_error_message(self, error: Exception, default_message: str) -> str:
        """Create safe error message that doesn't expose sensitive internals.

        This method sanitizes error messages to avoid exposing system paths
        while still providing useful debugging information.
        """
        error_str = str(error)

        # Check if this is a GitCommandError with useful details
        if "GitCommandError" in str(type(error).__name__) or "git" in error_str.lower():
            # For git errors, preserve the error message but sanitize absolute paths
            # Replace full paths with just the worktree name
            import re

            # Remove absolute paths but keep worktree names
            sanitized = re.sub(r"(/[^\s]+/)+([^/\s]+)(?=\s|$)", r"\2", error_str)
            return f"{default_message}: {sanitized}"

        # For other errors with potentially sensitive paths, be more careful
        error_lower = error_str.lower()
        if any(sensitive in error_lower for sensitive in ["/users/", "/home/", "c:\\"]):
            # Contains absolute paths, sanitize them
            return default_message

        # For permission errors, include them (they're useful for debugging)
        if "permission" in error_lower or "access" in error_lower:
            return f"{default_message}: {error_str}"

        # For errors about worktree state, include the details
        if any(
            keyword in error_lower
            for keyword in [
                "uncommitted",
                "untracked",
                "unpushed",
                "branch",
                "already exists",
                "not found",
            ]
        ):
            return f"{default_message}: {error_str}"

        # Default case: if unsure, include error details with the message
        return f"{default_message}: {error_str}" if error_str else default_message

    async def create_worktree(self, branch_name: str, base_branch: str = "main") -> WorktreeResult:
        """Create a new worktree with automation.

        Args:
            branch_name: Name of the new branch
            base_branch: Base branch to create from

        Returns:
            WorktreeResult with success status, worktree info, and any errors
        """

        # Validate branch name using value object
        validation_error = self._validate_branch_name(branch_name)
        if validation_error:
            return validation_error

        # Validate base branch
        base_validation_error = self._validate_branch_name(base_branch)
        if base_validation_error:
            return WorktreeResult.create_failure(
                f"Invalid base branch: {base_validation_error.errors[0]}"
            )

        # Generate worktree path
        try:
            worktree_path = self._generate_worktree_path(branch_name)
        except Exception as e:
            return WorktreeResult.create_failure(
                self._safe_error_message(e, "Failed to generate worktree path")
            )

        # Check if already exists
        if worktree_path.exists():
            return WorktreeResult.create_failure("A worktree with this name already exists")

        # Emit pre-creation event
        self.event_bus.emit(
            WorktreeEvents.CREATING,
            {"branch_name": branch_name, "base_branch": base_branch},
            source="WorktreeService",
        )

        try:
            # Create the worktree
            worktree = await self.git_repository.create_worktree(
                branch_name, worktree_path, base_branch
            )

            # Assign color
            color_result = await self.color_service.assign_color(branch_name)
            color = color_result.data if color_result.success else None

            # Execute post-creation hooks
            hook_result = await self.hook_service.execute_hooks(
                HookStage.POST_CREATE,
                {
                    "worktree": worktree.model_dump(),
                    "color": color.model_dump() if color else None,
                    "project": self._get_project_name(),
                    "worktree_name": branch_name,
                    "color_hex": getattr(color, "hex", None) if color else None,
                    "color_rgb": (
                        ",".join(map(str, color.rgb)) if color and hasattr(color, "rgb") else None
                    ),
                },
            )

            # Emit success event
            self.event_bus.emit(
                WorktreeEvents.CREATED,
                {"worktree": worktree.model_dump(), "color": color.model_dump() if color else None},
                source="WorktreeService",
            )

            # Prepare hook execution info
            hook_info = None
            if hook_result and hasattr(hook_result, "hook_details"):
                # Get hook execution summary from the custom attribute
                details = hook_result.hook_details
                total_hooks = (
                    len(details.get("executed", []))
                    + len(details.get("skipped", []))
                    + len(details.get("failed", []))
                )
                if total_hooks > 0:
                    hook_info = {
                        "executed": details.get("executed", []),
                        "skipped": details.get("skipped", []),
                        "failed": details.get("failed", []),
                        "total": total_hooks,
                    }

            result = WorktreeResult.create_success_with_color(worktree, color, hook_info)
            if hook_result.has_warnings():
                result.warnings.extend(hook_result.warnings)

            return result

        except Exception as e:
            return WorktreeResult.create_failure(
                self._safe_error_message(e, "Failed to create worktree")
            )

    async def delete_worktree(
        self, worktree_id: str, force: bool = False, delete_branch: bool = False
    ) -> WorktreeResult:
        """Delete a worktree with safety checks.

        Args:
            worktree_id: Unique identifier of the worktree
            force: Force deletion even if worktree has changes
            delete_branch: Also delete the git branch

        Returns:
            WorktreeResult with success status and any errors
        """
        # Find the worktree
        worktrees_result = await self.list_worktrees()
        if not worktrees_result.success:
            return WorktreeResult.create_failure_with_errors(worktrees_result.errors)

        if not worktrees_result.data:
            return WorktreeResult.create_failure(f"Worktree '{worktree_id}' not found")

        worktree = next((w for w in worktrees_result.data if w.id == worktree_id), None)

        if not worktree:
            return WorktreeResult.create_failure(f"Worktree '{worktree_id}' not found")

        # Check if this is the current worktree
        if worktree.is_current:
            return WorktreeResult.create_failure(
                f"Cannot delete the current worktree '{worktree_id}'. Please switch to another worktree first."
            )

        # Safety checks (unless forced)
        if not force:
            safety_result = await self._check_worktree_safety(worktree)
            if not safety_result.is_safe:
                return WorktreeResult.create_failure(
                    f"Worktree has {', '.join(safety_result.issues)}. Use --force to delete anyway"
                )

        # Emit pre-deletion event
        self.event_bus.emit(
            WorktreeEvents.DELETING,
            {
                "worktree": worktree.model_dump(),
                "force": force,
                "delete_branch": delete_branch,
            },
            source="WorktreeService",
        )

        # Execute pre-deletion hooks
        await self.hook_service.execute_hooks(
            HookStage.PRE_DELETE,
            {
                "worktree": worktree.model_dump(),
                "project": self._get_project_name(),
                "worktree_name": worktree.name,
            },
        )

        try:
            # Delete the worktree
            await self.git_repository.delete_worktree(worktree.path, force)

            # Delete branch if requested
            if delete_branch:
                await self.git_repository.delete_branch(worktree.branch, force)

            # Emit success event
            self.event_bus.emit(
                WorktreeEvents.DELETED,
                {
                    "worktree": worktree.model_dump(),
                    "worktree_id": worktree_id,
                    "branch_deleted": delete_branch,
                },
                source="WorktreeService",
            )

            return WorktreeResult(success=True, color=None, hook_info=None)

        except Exception as e:
            return WorktreeResult.create_failure(
                self._safe_error_message(e, "Failed to delete worktree")
            )

    async def get_worktree(self, worktree_id: str) -> WorktreeResult:
        """Get a specific worktree by ID.

        Args:
            worktree_id: ID of the worktree to retrieve

        Returns:
            WorktreeResult with the worktree data or error
        """
        try:
            list_result = await self.list_worktrees()
            if not list_result.success:
                return WorktreeResult.create_failure("Failed to list worktrees")

            # Find worktree by ID
            if not list_result.data:
                return WorktreeResult.create_failure(f"Worktree '{worktree_id}' not found")

            worktree = next((wt for wt in list_result.data if wt.id == worktree_id), None)
            if not worktree:
                return WorktreeResult.create_failure(f"Worktree '{worktree_id}' not found")

            return WorktreeResult.create_success_with_color(worktree)

        except Exception as e:
            return WorktreeResult.create_failure(
                self._safe_error_message(e, f"Failed to get worktree '{worktree_id}'")
            )

    async def list_worktrees(self) -> WorktreeListResult:
        """List all worktrees.

        Returns:
            WorktreeListResult with list of worktrees
        """
        try:
            worktrees = await self.git_repository.list_worktrees()

            # Sort by name for consistent display
            worktrees.sort(key=lambda w: w.name)

            return WorktreeListResult.create_success(worktrees)

        except Exception as e:
            return WorktreeListResult(
                success=False,
                data=[],
                total=0,
                errors=[self._safe_error_message(e, "Failed to list worktrees")],
            )

    async def switch_worktree(self, worktree_id: str) -> WorktreeResult:
        """Switch to a different worktree.

        Args:
            worktree_id: Unique identifier of the worktree to switch to

        Returns:
            WorktreeResult with success status and any errors
        """
        # Find the worktree
        worktrees_result = await self.list_worktrees()
        if not worktrees_result.success:
            return WorktreeResult.create_failure_with_errors(worktrees_result.errors)

        if not worktrees_result.data:
            return WorktreeResult.create_failure(f"Worktree '{worktree_id}' not found")

        worktree = next((w for w in worktrees_result.data if w.id == worktree_id), None)

        if not worktree:
            return WorktreeResult.create_failure(f"Worktree '{worktree_id}' not found")

        # Emit switching event
        self.event_bus.emit(
            WorktreeEvents.SWITCHING,
            {
                "from_worktree": await self._get_current_worktree(),
                "to_worktree": worktree.model_dump(),
            },
            source="WorktreeService",
        )

        # Execute pre-switch hooks
        await self.hook_service.execute_hooks(
            HookStage.PRE_SWITCH, {"worktree": worktree.model_dump()}
        )

        # The actual directory change is handled by the shell integration
        # We just emit an event for the shell to handle
        self.event_bus.emit(
            WorktreeEvents.SWITCHED,
            {"worktree": worktree.model_dump(), "path": str(worktree.path)},
            source="WorktreeService",
        )

        # Execute post-switch hooks
        await self.hook_service.execute_hooks(
            HookStage.POST_SWITCH, {"worktree": worktree.model_dump()}
        )

        return WorktreeResult.create_success_with_color(worktree)

    def _generate_worktree_path(self, branch_name: str) -> Path:
        """Generate secure path for new worktree based on branch name."""
        project_name = self._get_project_name()

        # Sanitize project name - prevent path traversal
        if not project_name or ".." in project_name or "/" in project_name or "\\" in project_name:
            raise ValueError("Invalid project name")

        base_dir = self.config.base_dir.replace("{project}", project_name)

        # Sanitize branch name for filesystem safety - prevent directory traversal
        safe_branch_name = (
            branch_name.replace("..", "_").replace("/", "_").replace("\\", "_").strip(".")
        )
        if not safe_branch_name:
            raise ValueError("Branch name results in empty path")

        # base_dir is relative to the repository itself, not its parent
        repo_path = Path(self.git_repository.repo_path)
        worktree_path = repo_path / base_dir / safe_branch_name

        # Resolve the path - worktrees can be siblings of the main repo
        try:
            resolved_path = worktree_path.resolve()

            # Security check: ensure no malicious path traversal beyond parent's parent
            # Allow worktrees to be created as siblings of the main repository
            repo_parent = repo_path.parent.resolve()
            repo_grandparent = repo_parent.parent.resolve()

            # Additional security: ensure the path is within reasonable length
            if len(str(resolved_path)) > 1000:
                raise ValueError("Generated path too long")

            if not (
                str(resolved_path).startswith(str(repo_path.resolve()))
                or str(resolved_path).startswith(str(repo_parent))
                or str(resolved_path).startswith(str(repo_grandparent))
            ):
                raise ValueError("Path escapes allowed boundaries")
        except (OSError, RuntimeError) as e:
            raise ValueError(f"Invalid path: {e}") from None

        return resolved_path

    def _get_project_name(self) -> str:
        """Get project name from repository.

        Returns:
            Name of the current project
        """
        return self.git_repository.repo_path.name

    async def _check_worktree_safety(self, worktree: Worktree) -> WorktreeSafetyResult:
        """Check if worktree is safe to delete.

        Args:
            worktree: Worktree to check

        Returns:
            WorktreeSafetyResult with safety status and issues
        """
        issues = []

        if await self.git_repository.has_uncommitted_changes(worktree.path):
            issues.append("uncommitted changes")

        if await self.git_repository.has_untracked_files(worktree.path):
            issues.append("untracked files")

        if await self.git_repository.has_unpushed_commits(worktree.path):
            issues.append("unpushed commits")

        return WorktreeSafetyResult(is_safe=len(issues) == 0, issues=issues)

    async def _get_current_worktree(self) -> dict[str, str] | None:
        """Get current worktree information.

        Returns:
            Dictionary with current worktree info or None
        """
        try:
            worktrees_result = await self.list_worktrees()
            if worktrees_result.success and worktrees_result.data:
                current = next((w for w in worktrees_result.data if w.is_current), None)
                return current.model_dump() if current else None
        except Exception:
            pass
        return None
