"""
General utilities common to all projects.

This module provides organized utility classes for:
- Storage and HuggingFace operations
- Git repository management
- Filesystem operations
- Command execution

All logging uses centralized utils.logger for consistency.
"""

import os
import pathlib
import subprocess
from pathlib import Path

from git import Repo
from rich import box
from rich.panel import Panel
from rich.progress import Progress, SpinnerColumn, TextColumn
from rich.status import Status
from rich.table import Table
from rich.text import Text
from rich.tree import Tree

from config import settings
from utils.logger import get_console, get_logger

# Get logger and console from centralized logging system
logger = get_logger(__name__)
console = get_console()


class HuggingFaceManager:
    """
    Manager for storage directories and HuggingFace operations.
    """

    def __init__(self):
        """Initialize the HuggingFaceManager."""
        pass

    def get_hf_token(self) -> str:
        """
        Get the HuggingFace token from configuration.

        Returns:
            str: The HuggingFace token

        Raises:
            ValueError: If HF_TOKEN is not set or empty
        """
        hf_token = settings.hf_token

        if not hf_token:
            error_panel = Panel(
                "[bold red]HuggingFace Token (HF_TOKEN) is not set![/bold red]\n\n"
                "[bold yellow]To fix this issue:[/bold yellow]\n\n"
                "[cyan]1. For Local Development:[/cyan]\n"
                "   • Create a .env file in your project directory\n"
                "   • Add: HF_TOKEN=your_actual_token_here\n\n"
                "[cyan]2. For HuggingFace Spaces:[/cyan]\n"
                "   • Go to your Space settings\n"
                "   • Add a new secret: HF_TOKEN = your_actual_token_here\n\n"
                "[cyan]3. For other environments:[/cyan]\n"
                "   • Set environment variable: export HF_TOKEN=your_actual_token_here\n\n"
                "[bold blue]To get your HuggingFace token:[/bold blue]\n"
                "   • Go to https://huggingface.co/settings/tokens\n"
                "   • Create a new token or copy an existing one\n"
                "   • Make sure it has 'read' permissions for the repository\n\n"
                "[italic]The token is required to access private repositories.[/italic]",
                title="[bold red]❌ Authentication Error[/bold red]",
                border_style="red",
            )
            console.print(error_panel)
            raise ValueError("HuggingFace Token (HF_TOKEN) is not set!")

        # Debug: Check token format (without exposing the actual token)
        token_info = Text()
        token_info.append("🔑 Token Info: ", style="cyan")
        token_info.append(f"Length: {len(hf_token)}, ", style="white")
        token_info.append(f"Format: {hf_token[:4]}...{hf_token[-4:]}", style="green")
        console.print(token_info)

        return hf_token

    def validate_hf_token(self) -> bool:
        """
        Validate the HuggingFace token by testing API access.

        Returns:
            bool: True if token is valid, False otherwise
        """
        try:
            from huggingface_hub import HfApi

            hf_token = self.get_hf_token()
            api = HfApi(token=hf_token)

            # Test token by getting user info
            user_info = api.whoami()

            console.print(
                f"[green]✅ Token validation successful.[/green] Logged in as: [bold cyan]{user_info['name']}[/bold cyan]"
            )

            return True

        except Exception as e:
            console.print(
                f"[red]❌[/red] Token validation failed: [bold red]{e}[/bold red]"
            )
            return False

    def sync_repo(self, repo_dir: pathlib.Path, repo_url: str):
        """
        Sync the repository from HuggingFace.
        Args:
            repo_dir (pathlib.Path): Path to the repo directory
            repo_url (str): URL template for the repository with {hf_token} placeholder
        """
        if not repo_dir.exists() or not any(repo_dir.iterdir()):
            console.print(
                f"[red]❌ Repository path {repo_dir} does not exist or is empty.[/red]"
            )
            raise RuntimeError(
                f"Repository path {repo_dir} does not exist or is empty."
            )
        else:
            console.print(
                f"[green]✅ Repository path {repo_dir} is accessible.[/green]"
            )

        git_manager = GitManager()

        if git_manager.repo_changes_detected(repo_path=repo_dir):
            console.print(
                "[yellow]🔄 Git changes detected, updating repository...[/yellow]"
            )
            with Progress(
                SpinnerColumn(),
                TextColumn("[progress.description]{task.description}"),
                console=console,
            ):
                git_manager.pull(repo_dir=repo_dir, repo_url=repo_url)
                if git_manager.wipe_cache_all(repo_dir=repo_dir):
                    console.print(
                        "[green]✅ Repository cache cleaned successfully[/green]"
                    )
                else:
                    console.print(
                        "[yellow]⚠️ Repository cache cleanup had warnings[/yellow]"
                    )
            console.print("[green]✅ Repository sync completed[/green]")
        else:
            console.print("[green]✅ Repository is up to date[/green]")

    def sync_repositories(self):
        """
        Sync all configured Hugging Face repositories.

        Loops through settings.repositories and syncs each repo to the configured
        hf_repo_dir location. This method is typically called at application startup.

        Returns:
            None

        Logs:
            - Info messages for each repository being synced
            - Warning if no repositories configured or HF_REPO_DIR not set
            - Error messages if syncing fails for any repository
        """
        if not settings.repositories:
            logger.info("[dim]No repositories configured for syncing[/dim]")
            return

        if not settings.hf_repo_dir:
            logger.warning(
                "[yellow]HF_REPO_DIR not configured, skipping repository sync[/yellow]"
            )
            return

        console.rule("[bold cyan]Syncing Repositories[/bold cyan]")

        for repo in settings.repositories:
            try:
                repo_local_dir = settings.hf_repo_dir / repo.repo_name
                logger.info(
                    f"[cyan]Syncing repository:[/cyan] [bold]{repo.repo_name}[/bold]"
                )
                logger.info(f"  [dim]URL:[/dim] {repo.repo_url}")
                logger.info(f"  [dim]Local:[/dim] {repo_local_dir}")

                self.sync_repo(repo_dir=repo_local_dir, repo_url=repo.repo_url)

            except Exception as e:
                logger.error(
                    f"[red]Error syncing repository {repo.repo_name}: {e}[/red]",
                    exc_info=True,
                )

        console.rule("[bold green]Repository Sync Complete[/bold green]")

    def running_in_space(self):
        """
        Detect if running in a HuggingFace Space environment.

        Returns:
            bool: True if running in HuggingFace Space, False otherwise
        """
        ret = os.name == "posix" and os.path.exists("/data")
        if ret:
            if not os.path.ismount("/data"):
                console.print(
                    "[red]❌ Detected /data path but it is not a mounted drive in HuggingFace Space.[/red]"
                )
                raise RuntimeError(
                    "Detected /data path but it is not a mounted drive in HuggingFace Space."
                )
        return ret


class GitManager:
    """
    Manager for Git operations including repository synchronization and cache management.
    """

    def __init__(self):
        """Initialize the GitManager."""
        self.hf_manager = HuggingFaceManager()
        self.fs_manager = FilesystemManager()

    def repo_changes_detected(self, repo_path: Path) -> bool:
        """
        Returns True if there are changes in the remote origin compared to the local branch.

        Args:
            repo_path: Path to the git repository

        Returns:
            bool: True if remote has changes, False otherwise

        Raises:
            RuntimeError: If no remote tracking branch is found for the current branch.
        """
        if not repo_path.exists():
            return True
        if repo_path.exists() and not any(repo_path.iterdir()):
            repo_path.rmdir()
            return True

        repo = Repo(repo_path)
        origin = repo.remotes.origin
        origin.fetch()
        local_branch = repo.active_branch
        remote_ref_name = f"origin/{local_branch.name}"
        remote_ref = next(
            (ref for ref in repo.refs if ref.name == remote_ref_name), None
        )

        if remote_ref is None:
            raise RuntimeError(
                f"No remote tracking branch found for {local_branch.name} (expected ref '{remote_ref_name}')"
            )

        return local_branch.commit != remote_ref.commit

    def pull(self, repo_dir: Path, repo_url: str) -> bool:
        """
        Clone or pull the repository with LFS support.

        Args:
            repo_dir: Path to the repo directory

        Returns:
            bool: True if successful, False otherwise
        """
        # First validate the token
        console.print("[cyan]🔐 Validating HuggingFace token...[/cyan]")
        if not self.hf_manager.validate_hf_token():
            console.print(
                "[red]❌ Token validation failed. Please check your HF_TOKEN.[/red]"
            )
            return False

        hf_token = self.hf_manager.get_hf_token()
        repo_url = repo_url.format(hf_token=hf_token)

        # Check if directory exists and is not empty
        if repo_dir.exists() and any(repo_dir.iterdir()):
            console.print(
                f"[blue]📁 Directory {repo_dir} exists. Pulling latest changes...[/blue]"
            )
            try:
                with Progress(
                    SpinnerColumn(),
                    TextColumn("[progress.description]{task.description}"),
                    console=console,
                ) as progress:
                    # Use Repo class to pull changes with LFS files as pointers only
                    # Set environment variable to skip LFS smudge filter
                    pull_env = os.environ.copy()
                    pull_env["GIT_LFS_SKIP_SMUDGE"] = "1"

                    repo = Repo(repo_dir)
                    origin = repo.remotes.origin

                    # Configure the git command to use our environment
                    with repo.git.custom_environment(**pull_env):
                        origin.pull()

                console.print(
                    "[green]✅ Git pull completed successfully with LFS files as pointers.[/green]"
                )

            except Exception as e:
                console.print(f"[red]❌ Error during git pull: {e}[/red]")
                return False
        else:
            console.print(
                f"[blue]📁 Directory {repo_dir} doesn't exist or is empty. Downloading repository...[/blue]"
            )

            # Use git clone with proper authentication for HuggingFace
            console.print("[cyan]📥 Downloading dataset using git...[/cyan]")

            # Ensure parent directory exists
            repo_dir.parent.mkdir(parents=True, exist_ok=True)

            try:
                with Progress(
                    SpinnerColumn(),
                    TextColumn("[progress.description]{task.description}"),
                    console=console,
                ) as progress:
                    task = progress.add_task("Cloning repository...", total=None)

                    # Clone repository using Repo class with LFS files as pointers only
                    # Set environment variable to skip LFS smudge filter (prevents downloading actual files)
                    clone_env = os.environ.copy()
                    clone_env["GIT_LFS_SKIP_SMUDGE"] = "1"

                    repo = Repo.clone_from(repo_url, str(repo_dir), env=clone_env)
                    progress.remove_task(task)

                console.print(
                    "[green]✅ Repository cloned successfully with LFS files as pointers.[/green]"
                )

            except Exception as e:
                console.print(f"[red]❌ Git clone failed: {e}[/red]")
                return False

        # Perform LFS pull to get actual files
        console.print("[cyan]📥 Pulling LFS files to get actual content...[/cyan]")
        try:
            original_cwd = os.getcwd()
            os.chdir(repo_dir)

            try:
                with Progress(
                    SpinnerColumn(),
                    TextColumn("[progress.description]{task.description}"),
                    console=console,
                ) as progress:
                    task = progress.add_task("Pulling LFS files...", total=None)

                    # Perform git lfs pull to download actual file content
                    subprocess.run(
                        ["git", "lfs", "pull"],
                        capture_output=True,
                        text=True,
                        timeout=1200,
                        check=False,  # Don't raise on non-zero exit
                    )
                    progress.remove_task(task)

            finally:
                os.chdir(original_cwd)

        except subprocess.TimeoutExpired:
            console.print(
                "[red]❌ LFS pull timed out. Large files may take longer to download.[/red]"
            )
            return False
        except Exception as e:
            console.print(f"[red]❌ Error during LFS pull: {e}[/red]")
            return False

        return True

    def wipe_cache_all(self, repo_dir: Path) -> bool:
        """
        Wipe all cached files in .git folder using standard git and git lfs commands.

        This function safely cleans up various types of cached data:
        - Git LFS cache and objects
        - Temporary files and logs

        Args:
            repo_dir: Path to the Git repository

        Returns:
            bool: True if successful, False otherwise
        """
        console.print(
            f"[cyan]🧹 Starting Git cache cleanup for:[/cyan] [bold blue]{repo_dir}[/bold blue]"
        )

        try:
            if not (repo_dir / ".git").exists():
                console.print(f"[red]❌ Not a Git repository: {repo_dir}[/red]")
                return False

            # Get initial .git size
            git_dir = repo_dir / ".git"
            initial_size = self.fs_manager.get_directory_size(git_dir)
            console.print(
                f"[cyan]📊 Initial .git directory size:[/cyan] [bold white]{self.fs_manager.format_bytes(initial_size)}[/bold white]"
            )

            # Change to repository directory
            original_cwd = os.getcwd()
            os.chdir(repo_dir)

            try:
                # Check for remaining large LFS folder and force clean if needed
                lfs_dir = git_dir / "lfs"
                if lfs_dir.exists():
                    lfs_size = self.fs_manager.get_directory_size(lfs_dir)
                    if lfs_size > 0:
                        console.print(
                            f"[yellow]⚠️ LFS directory exists ({self.fs_manager.format_bytes(lfs_size)}). Cleaning...[/yellow]"
                        )

                # Clean LFS tmp directory
                temp_dirs_cleaned = 0
                lfs_tmp_dir = git_dir / "lfs" / "tmp"
                if lfs_tmp_dir.exists():
                    import shutil

                    shutil.rmtree(lfs_tmp_dir, ignore_errors=True)
                    temp_dirs_cleaned += 1
                    console.print("[green]✅ Cleaned LFS tmp directory[/green]")

                # Clean git tmp files
                for tmp_pattern in ["tmp_*", "*.tmp"]:
                    import glob

                    for tmp_file in glob.glob(str(git_dir / tmp_pattern)):
                        try:
                            os.remove(tmp_file)
                            temp_dirs_cleaned += 1
                        except Exception as e:
                            logger.debug(f"Could not remove temp file {tmp_file}: {e}")

                if temp_dirs_cleaned > 0:
                    console.print(
                        f"[green]✅ Cleaned {temp_dirs_cleaned} temporary file(s)[/green]"
                    )
                else:
                    console.print("[blue]ℹ️ No temporary files found[/blue]")

                # Final lightweight GC
                with Status("[cyan]🧹 Final cleanup...", console=console):
                    result = subprocess.run(
                        ["git", "gc", "--auto"],
                        capture_output=True,
                        text=True,
                        timeout=300,
                    )
                if result.returncode == 0:
                    console.print("[green]✅ Git garbage collection completed[/green]")
                else:
                    console.print("[yellow]⚠️ Git gc completed with warnings[/yellow]")

            finally:
                os.chdir(original_cwd)

            # Get final .git size
            final_size = self.fs_manager.get_directory_size(git_dir)
            saved_space = initial_size - final_size

            console.print("")
            console.print("[bold cyan]📊 Cleanup Results:[/bold cyan]")
            console.print(
                f"[white]Initial size:[/white] [bold]{self.fs_manager.format_bytes(initial_size)}[/bold]"
            )
            console.print(
                f"[white]Final size:[/white]   [bold]{self.fs_manager.format_bytes(final_size)}[/bold]"
            )
            console.print(
                f"[white]Space saved:[/white]  [bold green]{self.fs_manager.format_bytes(saved_space)}[/bold green]"
            )

            if saved_space > 0:
                percentage_saved = (saved_space / initial_size) * 100
                console.print(
                    f"[white]Reduction:[/white]    [bold green]{percentage_saved:.1f}%[/bold green]"
                )
            elif saved_space == 0:
                console.print(
                    "[blue]No space saved - repository was already clean[/blue]"
                )
            else:
                console.print(
                    "[yellow]Size increased (this can happen due to repacking)[/yellow]"
                )

            console.print("")
            console.print("[green]✅ Git cache cleanup completed successfully![/green]")

            # Create summary panel
            summary_panel = Panel(
                "[bold green]✅ Cache Cleanup Summary[/bold green]\n\n"
                "• LFS cached objects and temporary files\n"
                "• Git reflog entries and unreachable objects\n"
                "• Packed and optimized git object database\n"
                "• Temporary files and lock files\n"
                f"• Total space saved: [bold]{self.fs_manager.format_bytes(saved_space)}[/bold]",
                title="[bold blue]🧹 Git Cache Cleanup[/bold blue]",
                border_style="green",
            )
            console.print(summary_panel)

            return True

        except subprocess.TimeoutExpired:
            console.print(
                "[red]❌ Operation timed out. The repository might have a very large cache.[/red]"
            )
            return False
        except Exception as e:
            console.print(f"[red]❌ Error cleaning git cache:[/red] {e}")
            return False


class FilesystemManager:
    """
    Manager for filesystem operations including directory browsing and disk usage analysis.
    """

    def __init__(self):
        """Initialize the FilesystemManager."""
        pass

    def format_bytes(self, bytes_value: int) -> str:
        """
        Format bytes to human readable format.

        Args:
            bytes_value: Size in bytes

        Returns:
            str: Formatted string with appropriate unit (GB, MB, KB, or bytes)
        """
        if bytes_value >= 1024 * 1024 * 1024:  # GB
            return f"{bytes_value / (1024 * 1024 * 1024):.2f} GB"
        elif bytes_value >= 1024 * 1024:  # MB
            return f"{bytes_value / (1024 * 1024):.1f} MB"
        elif bytes_value >= 1024:  # KB
            return f"{bytes_value / 1024:.1f} KB"
        else:
            return f"{bytes_value} bytes"

    def get_directory_size(self, directory_path: Path) -> int:
        """
        Get the total size of a directory and all its contents.

        Args:
            directory_path: Path to the directory

        Returns:
            int: Total size in bytes
        """
        total_size = 0
        try:
            for dirpath, dirnames, filenames in os.walk(directory_path):
                for filename in filenames:
                    filepath = os.path.join(dirpath, filename)
                    try:
                        total_size += os.path.getsize(filepath)
                    except (OSError, FileNotFoundError):
                        # Skip files that can't be accessed
                        pass
        except (OSError, PermissionError):
            # Skip directories that can't be accessed
            pass
        return total_size

    def browse_files(self, directory_path: Path) -> str:
        """
        Browse files in a given directory with web-friendly formatting.

        Args:
            directory_path: Path to the directory to browse

        Returns:
            str: Formatted string with directory contents
        """
        try:
            if not directory_path.exists():
                return f"❌ Directory {directory_path} does not exist"

            # Create plain text output for web display
            output_lines = []
            output_lines.append("📂 Directory Browser")
            output_lines.append("=" * 60)
            output_lines.append(f"📂 {directory_path}")
            output_lines.append("")

            # Add parent directory link if not at root
            if str(directory_path) != "/":
                parent = str(directory_path.parent)
                output_lines.append(f"📁 .. (parent: {parent})")

            # Separate directories and files for better organization
            directories = []
            files = []

            for item in sorted(directory_path.iterdir()):
                if item.is_dir():
                    try:
                        item_count = len(list(item.iterdir()))
                        directories.append(f"📁 {item.name}/ ({item_count} items)")
                    except Exception as e:
                        logger.debug(f"Could not access directory {item}: {e}")
                        directories.append(f"📁 {item.name}/ (access error)")
                else:
                    try:
                        size = item.stat().st_size
                        size_str = self.format_bytes(size)
                        files.append(f"📄 {item.name} ({size_str})")
                    except Exception as e:
                        logger.debug(f"Could not get size for file {item}: {e}")
                        files.append(f"📄 {item.name} (size unknown)")

            # Add directories first, then files
            for directory in directories:
                output_lines.append(directory)

            if files:
                output_lines.append("")  # Separator between dirs and files
                for file in files:
                    output_lines.append(file)

            # Also log to console with rich formatting for debugging
            tree = Tree(f"📂 [bold blue]{directory_path}[/bold blue]")

            # Add parent directory link if not at root
            if str(directory_path) != "/":
                parent = str(directory_path.parent)
                tree.add(f"📁 [dim].. (parent: {parent})[/dim]")

            # Add items to tree for console display
            for item in sorted(directory_path.iterdir()):
                if item.is_dir():
                    try:
                        item_count = len(list(item.iterdir()))
                        tree.add(
                            f"📁 [bold cyan]{item.name}/[/bold cyan] [dim]({item_count} items)[/dim]"
                        )
                    except Exception as e:
                        logger.debug(f"Could not access directory {item} for tree: {e}")
                        tree.add(
                            f"📁 [bold cyan]{item.name}/[/bold cyan] [red](access error)[/red]"
                        )
                else:
                    try:
                        size = item.stat().st_size
                        size_str = self.format_bytes(size)
                        tree.add(
                            f"📄 [white]{item.name}[/white] [dim]({size_str})[/dim]"
                        )
                    except Exception as e:
                        logger.debug(f"Could not get size for file {item} in tree: {e}")
                        tree.add(
                            f"📄 [white]{item.name}[/white] [red](size unknown)[/red]"
                        )

            # Display rich version in console for debugging
            panel = Panel(
                tree,
                title="[bold green]📂 Directory Browser[/bold green]",
                border_style="green",
                box=box.ROUNDED,
            )
            console.print(panel)

            # Return plain text for web display
            return "\n".join(output_lines)

        except Exception as e:
            console.print(f"[red]❌ Error browsing {directory_path}: {str(e)}[/red]")
            return f"❌ Error browsing {directory_path}: {str(e)}"

    def debug_list_directories(self, path: str = "/"):
        """
        Debug function to list all directories in a given path with rich formatting.

        Args:
            path: Path to list directories from (default is root "/")
        """
        console.print(
            Panel.fit(
                f"[bold blue]Debugging Directory Listing[/bold blue]\n[cyan]Path: {path}[/cyan]",
                border_style="blue",
            )
        )

        try:
            path_obj = pathlib.Path(path)

            if not path_obj.exists():
                console.print(f"[red]❌ Path {path} does not exist![/red]")
                return

            # Create a table for better visualization
            table = Table(title=f"Contents of {path}", box=box.ROUNDED)
            table.add_column("Type", style="cyan", width=8)
            table.add_column("Name", style="white")
            table.add_column("Details", style="dim")

            for item in sorted(path_obj.iterdir()):
                if item.is_dir():
                    try:
                        # Try to count items in directory
                        item_count = len(list(item.iterdir()))
                        table.add_row("📁 DIR", item.name + "/", f"{item_count} items")
                    except PermissionError:
                        table.add_row(
                            "📁 DIR", item.name + "/", "[red]permission denied[/red]"
                        )
                    except Exception as e:
                        table.add_row(
                            "📁 DIR", item.name + "/", f"[red]error: {e}[/red]"
                        )
                else:
                    try:
                        file_size = item.stat().st_size if item.exists() else 0
                        size_str = self.format_bytes(file_size)
                        table.add_row("📄 FILE", item.name, size_str)
                    except Exception as e:
                        table.add_row("📄 FILE", item.name, f"[red]error: {e}[/red]")

            console.print(table)

        except Exception as e:
            console.print(f"[red]❌ Error listing directory {path}: {e}[/red]")

            # Try alternative method with os.listdir
            try:
                console.print(
                    f"\n[yellow]🔄 Trying alternative method for {path}:[/yellow]"
                )
                items = os.listdir(path)

                alt_table = Table(title="Alternative Listing Method", box=box.SIMPLE)
                alt_table.add_column("Type", style="cyan")
                alt_table.add_column("Name", style="white")

                for item in sorted(items):
                    item_path = os.path.join(path, item)
                    if os.path.isdir(item_path):
                        alt_table.add_row("📁", item + "/")
                    else:
                        alt_table.add_row("📄", item)

                console.print(alt_table)
            except Exception as e2:
                console.print(f"[red]❌ Alternative method also failed: {e2}[/red]")

    def scan_disk_usage(self, root_path: Path) -> str:
        """
        Scan disk usage for directories and subdirectories.

        Args:
            root_path: Root path to scan

        Returns:
            str: Formatted string with disk usage analysis
        """
        try:
            if not root_path.exists():
                return f"❌ Directory {root_path} does not exist"

            # Create plain text output for web display
            output_lines = []
            output_lines.append("💾 Disk Usage Analysis")
            output_lines.append("=" * 60)
            output_lines.append(f"📂 Root: {root_path}")
            output_lines.append("")

            # Get total size of root directory
            total_root_size = self.get_directory_size(root_path)
            output_lines.append(f"📊 Total Size: {self.format_bytes(total_root_size)}")
            output_lines.append("")

            # Collect directory sizes
            dir_sizes = []

            # Scan immediate subdirectories
            try:
                for item in root_path.iterdir():
                    if item.is_dir():
                        try:
                            dir_size = self.get_directory_size(item)
                            dir_sizes.append((item.name, dir_size, item))
                        except (OSError, PermissionError):
                            dir_sizes.append((item.name, 0, item))
            except (OSError, PermissionError):
                return f"❌ Permission denied accessing {root_path}"

            # Sort by size (largest first)
            dir_sizes.sort(key=lambda x: x[1], reverse=True)

            # Display directory sizes
            output_lines.append("📁 Directory Sizes (sorted by size):")
            output_lines.append("-" * 40)

            for dir_name, size, dir_path in dir_sizes:
                size_str = self.format_bytes(size)
                percentage = (
                    (size / total_root_size * 100) if total_root_size > 0 else 0
                )
                output_lines.append(
                    f"📁 {dir_name:<25} {size_str:>12} ({percentage:5.1f}%)"
                )

                # Show top 3 subdirectories for each directory
                try:
                    subdirs = []
                    for subitem in dir_path.iterdir():
                        if subitem.is_dir():
                            try:
                                subdir_size = self.get_directory_size(subitem)
                                subdirs.append((subitem.name, subdir_size))
                            except (OSError, PermissionError):
                                pass

                    # Sort subdirectories by size and show top 3
                    subdirs.sort(key=lambda x: x[1], reverse=True)
                    for subdir_name, subdir_size in subdirs[:3]:
                        if subdir_size > 0:
                            subsize_str = self.format_bytes(subdir_size)
                            output_lines.append(
                                f"  └─ {subdir_name:<23} {subsize_str:>12}"
                            )

                    if len(subdirs) > 3:
                        output_lines.append(
                            f"  └─ ... and {len(subdirs) - 3} more subdirectories"
                        )

                except (OSError, PermissionError):
                    output_lines.append("  └─ (unable to scan subdirectories)")

                output_lines.append("")

            # Add file count information
            output_lines.append("📄 File Statistics:")
            output_lines.append("-" * 40)

            total_files = 0
            total_dirs = 0

            try:
                for root, dirs, files in os.walk(root_path):
                    total_files += len(files)
                    total_dirs += len(dirs)
            except (OSError, PermissionError):
                pass

            output_lines.append(f"📄 Total Files: {total_files:,}")
            output_lines.append(f"📁 Total Directories: {total_dirs:,}")

            return "\n".join(output_lines)

        except Exception as e:
            console.print(f"[red]❌ Error scanning disk usage:[/red] {e}")
            return f"❌ Error scanning disk usage: {str(e)}"


class CommandManager:
    """
    Manager for executing shell commands with formatted output.
    """

    def __init__(self):
        """Initialize the CommandManager."""
        pass

    def run_command(self, command: str) -> str:
        """
        Run a shell command and return the output with web-friendly formatting.

        Args:
            command: Shell command to execute

        Returns:
            str: Formatted output of command execution
        """
        try:
            if not command.strip():
                return "⚠️ Please enter a command"

            with Status(
                f"[cyan]Running command: [bold]{command}[/bold]", console=console
            ):
                result = subprocess.run(
                    command, shell=True, capture_output=True, text=True
                )

            # Create plain text output for web display
            output_lines = []

            # Command info
            output_lines.append("Command Execution Results")
            output_lines.append("=" * 50)
            output_lines.append(f"Command: {command}")
            status_text = (
                "0 (Success)"
                if result.returncode == 0
                else f"{result.returncode} (Error)"
            )
            output_lines.append(f"Exit code: {status_text}")
            output_lines.append("")

            # STDOUT section
            if result.stdout:
                output_lines.append("📤 STDOUT:")
                output_lines.append("-" * 20)
                # Split long output into manageable chunks
                stdout_lines = result.stdout.strip().split("\n")
                if len(stdout_lines) > 50:
                    output_lines.extend(stdout_lines[:25])
                    output_lines.append(
                        f"... ({len(stdout_lines) - 50} lines omitted) ..."
                    )
                    output_lines.extend(stdout_lines[-25:])
                else:
                    output_lines.append(result.stdout.strip())
                output_lines.append("")

            # STDERR section
            if result.stderr:
                output_lines.append("📥 STDERR:")
                output_lines.append("-" * 20)
                stderr_lines = result.stderr.strip().split("\n")
                if len(stderr_lines) > 25:
                    output_lines.extend(stderr_lines[:15])
                    output_lines.append(
                        f"... ({len(stderr_lines) - 25} lines omitted) ..."
                    )
                    output_lines.extend(stderr_lines[-10:])
                else:
                    output_lines.append(result.stderr.strip())

            # Also log to console with rich formatting for debugging
            rich_content = []
            rich_content.append(f"[bold cyan]Command:[/bold cyan] {command}")
            rich_content.append(
                f"[bold cyan]Exit code:[/bold cyan] {'[green]0 (Success)' if result.returncode == 0 else f'[red]{result.returncode} (Error)'}"
            )
            rich_content.append("")

            if result.stdout:
                rich_content.append("[bold green]📤 STDOUT:[/bold green]")
                rich_content.append(result.stdout.strip())
                rich_content.append("")

            if result.stderr:
                rich_content.append("[bold red]📥 STDERR:[/bold red]")
                rich_content.append(result.stderr.strip())

            # Display rich version in console
            border_style = "green" if result.returncode == 0 else "red"
            title_style = (
                "✅ Command Successful"
                if result.returncode == 0
                else "❌ Command Failed"
            )

            panel = Panel(
                "\n".join(rich_content),
                title=f"[bold]{title_style}[/bold]",
                border_style=border_style,
                box=box.ROUNDED,
            )
            console.print(panel)

            # Return plain text for web display
            return "\n".join(output_lines)

        except subprocess.TimeoutExpired:
            console.print(
                f"[red]⏰ Command '{command}' timed out after 30 seconds[/red]"
            )
            return f"⏰ Command '{command}' timed out after 30 seconds"
        except Exception as e:
            console.print(f"[red]❌ Error running command: {str(e)}[/red]")
            return f"❌ Error running command: {str(e)}"
