"""Local filesystem storage backend for backups."""

from __future__ import annotations

import os
import shutil
import stat
from pathlib import Path

from gaol.backup.exceptions import BackupNotFoundError, StorageError
from gaol.backup.storage.base import StorageBackend
from gaol.config import get_data_dir


def _get_default_backup_dir() -> Path:
    """Get the default backup data directory."""
    return get_data_dir() / "backup" / "data"


class LocalStorageBackend(StorageBackend):
    """Local filesystem storage backend.

    Stores backups in a local directory with proper permissions.

    Example:
        >>> storage = LocalStorageBackend()
        >>> storage.upload(Path("/tmp/backup.tar.gz"), "mycontainer/backup.tar.gz")
        '/home/user/.local/share/gaol/backup/data/mycontainer/backup.tar.gz'
    """

    def __init__(self, base_path: Path | None = None) -> None:
        """Initialize local storage backend.

        Args:
            base_path: Base directory for backup storage.
                      Defaults to ~/.local/share/gaol/backup/data/
        """
        self._base_path = base_path or _get_default_backup_dir()

    @property
    def name(self) -> str:
        """Return the backend name."""
        return "local"

    @property
    def base_path(self) -> Path:
        """Get the base storage path."""
        return self._base_path

    def is_configured(self) -> bool:
        """Local storage is always available."""
        return True

    def _ensure_base_dir(self) -> None:
        """Ensure base directory exists with proper permissions."""
        self._base_path.mkdir(parents=True, exist_ok=True)
        os.chmod(self._base_path, stat.S_IRWXU)  # 700

    def _get_full_path(self, remote_path: str) -> Path:
        """Convert remote path to full local path."""
        return self._base_path / remote_path

    def upload(
        self,
        local_path: Path,
        remote_path: str,
        metadata: dict[str, str] | None = None,
    ) -> str:
        """Upload (copy) a file to local storage.

        Args:
            local_path: Source file path
            remote_path: Destination path relative to base
            metadata: Ignored for local storage

        Returns:
            Full path to the stored file
        """
        self._ensure_base_dir()

        dest_path = self._get_full_path(remote_path)
        dest_path.parent.mkdir(parents=True, exist_ok=True)

        try:
            # Copy file
            shutil.copy2(local_path, dest_path)
            # Set permissions (readable/writable by owner only)
            os.chmod(dest_path, stat.S_IRUSR | stat.S_IWUSR)  # 600
            return str(dest_path)
        except OSError as e:
            raise StorageError("local", "upload", str(e)) from e

    def download(self, remote_path: str, local_path: Path) -> None:
        """Download (copy) a file from local storage.

        Args:
            remote_path: Source path relative to base
            local_path: Destination file path
        """
        src_path = self._get_full_path(remote_path)

        if not src_path.exists():
            # Extract container and backup name from path
            parts = remote_path.split("/", 1)
            container = parts[0] if parts else "unknown"
            backup = parts[1] if len(parts) > 1 else remote_path
            raise BackupNotFoundError(container, backup)

        try:
            local_path.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy2(src_path, local_path)
        except OSError as e:
            raise StorageError("local", "download", str(e)) from e

    def delete(self, remote_path: str) -> None:
        """Delete a file from local storage.

        Args:
            remote_path: Path relative to base
        """
        full_path = self._get_full_path(remote_path)

        try:
            if full_path.exists():
                full_path.unlink()

                # Clean up empty parent directories
                parent = full_path.parent
                while parent != self._base_path:
                    if parent.exists() and not any(parent.iterdir()):
                        parent.rmdir()
                        parent = parent.parent
                    else:
                        break
        except OSError as e:
            raise StorageError("local", "delete", str(e)) from e

    def exists(self, remote_path: str) -> bool:
        """Check if a file exists in local storage."""
        return self._get_full_path(remote_path).exists()

    def list(self, prefix: str) -> list[str]:
        """List files with the given prefix.

        Args:
            prefix: Path prefix (e.g., "mycontainer/")

        Returns:
            List of relative paths matching the prefix
        """
        prefix_path = self._get_full_path(prefix)

        if not prefix_path.exists():
            # Check if it's a partial path
            parent = prefix_path.parent
            if not parent.exists():
                return []

            # List files matching the prefix
            name_prefix = prefix_path.name
            result = []
            for item in parent.iterdir():
                if item.name.startswith(name_prefix):
                    rel_path = str(item.relative_to(self._base_path))
                    if item.is_file():
                        result.append(rel_path)
                    elif item.is_dir():
                        for child in item.rglob("*"):
                            if child.is_file():
                                result.append(str(child.relative_to(self._base_path)))
            return sorted(result)

        # If prefix is a directory, list all files
        if prefix_path.is_dir():
            result = []
            for item in prefix_path.rglob("*"):
                if item.is_file():
                    result.append(str(item.relative_to(self._base_path)))
            return sorted(result)

        # If prefix is a file, return it
        if prefix_path.is_file():
            return [prefix]

        return []

    def get_size(self, remote_path: str) -> int:
        """Get the size of a file in bytes."""
        full_path = self._get_full_path(remote_path)

        if not full_path.exists():
            parts = remote_path.split("/", 1)
            container = parts[0] if parts else "unknown"
            backup = parts[1] if len(parts) > 1 else remote_path
            raise BackupNotFoundError(container, backup)

        return full_path.stat().st_size

    def get_total_size(self, prefix: str) -> int:
        """Get total size of all files with the given prefix."""
        files = self.list(prefix)
        total = 0
        for f in files:
            try:
                total += self.get_size(f)
            except BackupNotFoundError:
                pass
        return total
