"""Executable wrapping functionality for electron-angle-patcher."""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from electron_angle_patcher.config import Config
from electron_angle_patcher.models import PatchResult
from electron_angle_patcher.utils import (
    can_write_to_path,
    resign_application,
    strip_extended_attributes,
)

if TYPE_CHECKING:
    from pathlib import Path

logger = logging.getLogger(__name__)


class ExecutableWrapper:
    """Wrapper for Chromium/Electron app executables to pass command-line arguments.

    This class handles the wrapping of executable files to inject additional
    command-line arguments at runtime.
    """

    def __init__(self, app_path: Path, executable_name: str) -> None:
        """Initialize the executable wrapper.

        Args:
            app_path: Path to the application bundle.
            executable_name: Name of the executable file to wrap.
        """
        self._app_path = app_path.expanduser()
        self._macos_path = self._app_path / "Contents" / "MacOS"
        self._executable_name = executable_name
        self._executable_path = self._macos_path / self._executable_name
        self._original_path = self._macos_path / f"{self._executable_name}.original"

    @property
    def app_path(self) -> Path:
        """Get the application path."""
        return self._app_path

    @property
    def original_path(self) -> Path:
        """Get the backup/original executable path."""
        return self._original_path

    def _is_already_wrapped(self) -> bool:
        """Check if the executable is already wrapped.

        Returns:
            True if already wrapped and backup exists, False otherwise.
        """
        if not self._original_path.exists():
            return False

        try:
            first_line = self._executable_path.read_text().splitlines()[0]
            return first_line.startswith("#!")
        except (OSError, IndexError, UnicodeDecodeError):
            return False

    def _is_wrapper_without_backup(self) -> bool:
        """Check for error state: wrapper exists but no backup.

        Returns:
            True if wrapper exists without backup (error state).
        """
        if self._original_path.exists():
            return False

        try:
            first_line = self._executable_path.read_text().splitlines()[0]
            return first_line.startswith("#!")
        except (OSError, IndexError, UnicodeDecodeError):
            return False

    def _backup_executable(self) -> bool:
        """Create a backup of the original executable.

        Returns:
            True if backup succeeded, False otherwise.
        """
        import shutil

        try:
            shutil.copy2(self._executable_path, self._original_path)
            logger.info("  Backed up original to %s", self._original_path.name)
            return True
        except OSError as e:
            logger.error("  Error creating backup: %s", e)
            return False

    def _create_wrapper(self, extra_args: list[str]) -> bool:
        """Create the wrapper script.

        Args:
            extra_args: Additional arguments to pass to the executable.

        Returns:
            True if wrapper creation succeeded, False otherwise.
        """
        args_str = " ".join(extra_args)
        wrapper_script = Config.WRAPPER_TEMPLATE.format(
            original_executable=f"{self._executable_name}.original",
            args=args_str,
        )

        try:
            self._executable_path.write_text(wrapper_script)
            logger.info("  Created wrapper script")
            return True
        except OSError as e:
            logger.error("  Error writing wrapper: %s", e)
            return False

    def _make_executable(self) -> bool:
        """Make the wrapper script executable.

        Returns:
            True if successful, False otherwise.
        """
        try:
            self._executable_path.chmod(Config.EXECUTABLE_PERMISSIONS)
            logger.info("  Made wrapper executable")
            return True
        except OSError as e:
            logger.error("  Error setting permissions: %s", e)
            return False

    def patch(self, extra_args: list[str] | None = None) -> PatchResult:
        """Wrap executable to pass extra arguments.

        Args:
            extra_args: Additional arguments to pass (default: ["--use-angle=gl"]).

        Returns:
            PatchResult indicating the outcome.
        """
        if extra_args is None:
            extra_args = ["--use-angle=gl"]

        # Validate paths
        if not self._app_path.exists():
            logger.info("  App not found at %s", self._app_path)
            return PatchResult.NOT_FOUND

        if not self._executable_path.exists():
            logger.info("  Executable not found at %s", self._executable_path)
            return PatchResult.NOT_FOUND

        # Check for existing wrapper
        if self._is_already_wrapped():
            logger.info("  %s already patched (backup exists)", self._executable_name)
            return PatchResult.ALREADY_PATCHED

        # Check for error state
        if self._is_wrapper_without_backup():
            logger.error("  Error: Current executable is a wrapper script but no backup exists")
            logger.error("  This is an unexpected state. Aborting to avoid data loss.")
            return PatchResult.ERROR

        # Check permissions
        if not can_write_to_path(self._executable_path):
            logger.error("  Error: Cannot write to app")
            logger.error("  Run this script with sudo: sudo python electron_angle_patcher.py")
            return PatchResult.PERMISSION_DENIED

        logger.info("  Patching %s...", self._executable_name)

        # Perform patching operations
        if not self._backup_executable():
            return PatchResult.ERROR

        if not self._create_wrapper(extra_args):
            return PatchResult.ERROR

        if not self._make_executable():
            return PatchResult.ERROR

        # Non-critical operations
        if strip_extended_attributes(self._app_path):
            logger.info("  Stripped extended attributes")

        if resign_application(self._app_path):
            logger.info("  Re-signed application successfully")

        logger.info("  %s patch complete", self._executable_name)
        return PatchResult.SUCCESS


class BraveExecutablePatcher:
    """Convenience wrapper for Brave Browser using ExecutableWrapper."""

    def __init__(self, app_path: Path = Config.DEFAULT_BRAVE_PATH) -> None:
        """Initialize the Brave patcher.

        Args:
            app_path: Path to Brave Browser.app.
        """
        self._wrapper = ExecutableWrapper(app_path, "Brave Browser")

    def patch(self) -> PatchResult:
        """Patch Brave Browser executable.

        Returns:
            PatchResult indicating the outcome.
        """
        return self._wrapper.patch()


class ChromeExecutablePatcher:
    """Convenience wrapper for Google Chrome using ExecutableWrapper."""

    def __init__(self, app_path: Path = Config.DEFAULT_CHROME_PATH) -> None:
        """Initialize the Chrome patcher.

        Args:
            app_path: Path to Google Chrome.app.
        """
        self._wrapper = ExecutableWrapper(app_path, "Google Chrome")

    def patch(self) -> PatchResult:
        """Patch Google Chrome executable.

        Returns:
            PatchResult indicating the outcome.
        """
        return self._wrapper.patch()


class LghubPatcher:
    """Patches the Electron component (lghub_ui.app) of Logitech G Hub."""

    def __init__(self, app_path: Path = Config.DEFAULT_LGHUB_PATH) -> None:
        """Initialize the lghub patcher.

        Args:
            app_path: Path to lghub.app.
        """
        self._app_path = app_path.expanduser()
        # Only lghub_ui is Electron-based; the others are Qt or native
        self._wrapper = ExecutableWrapper(
            self._app_path / "Contents" / "MacOS" / "lghub_ui.app",
            "lghub_ui",
        )

    @property
    def app_path(self) -> Path:
        """Get the application path."""
        return self._app_path

    def patch(self) -> PatchResult:
        """Patch the lghub Electron component.

        Returns:
            PatchResult indicating the outcome.
        """
        if not self._app_path.exists():
            logger.info("  lghub not found at %s", self._app_path)
            return PatchResult.NOT_FOUND

        logger.info("  Patching lghub Electron app (lghub_ui.app)...")
        return self._wrapper.patch()
