"""
Local filesystem storage backend.

Provides storage operations using the local filesystem.
"""

import shutil
from pathlib import Path

from shared.storage.base import (
    FileNotFoundStorageError,
    StorageBackend,
    StorageError,
)


class LocalStorageBackend(StorageBackend):
    """Storage backend using local filesystem.

    Files are stored relative to a base path on the local filesystem.
    """

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

        Args:
            base_path: Base directory for all storage operations.
                       Will be created if it doesn't exist.
        """
        self._base_path = Path(base_path)
        self._base_path.mkdir(parents=True, exist_ok=True)

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

    def _get_full_path(self, remote_path: str) -> Path:
        """Convert a remote path to a full local path with security validation.

        Args:
            remote_path: The remote path to resolve.

        Returns:
            The full local path.

        Raises:
            StorageError: If the path attempts to escape the base directory.
        """
        # Reject absolute paths
        if remote_path.startswith("/") or (len(remote_path) > 1 and remote_path[1] == ":"):
            raise StorageError(f"Absolute paths not allowed: {remote_path}")

        # Resolve to prevent path traversal attacks
        full_path = (self._base_path / remote_path).resolve()
        base_resolved = self._base_path.resolve()

        # Verify the resolved path is within base_path
        try:
            full_path.relative_to(base_resolved)
        except ValueError:
            raise StorageError(f"Path traversal not allowed: {remote_path}")

        return full_path

    def upload(
        self, local_path: Path, remote_path: str, overwrite: bool = False
    ) -> str:
        """Upload a file to local storage.

        Args:
            local_path: Path to the local file to upload.
            remote_path: Destination path in storage.
            overwrite: If True, overwrite existing file.

        Returns:
            The remote path where the file was stored.

        Raises:
            FileNotFoundStorageError: If local_path doesn't exist.
            StorageError: If file exists and overwrite is False.
        """
        if not local_path.exists():
            raise FileNotFoundStorageError(str(local_path))

        dest_path = self._get_full_path(remote_path)

        if dest_path.exists() and not overwrite:
            raise StorageError(f"File already exists: {remote_path}")

        dest_path.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(local_path, dest_path)

        return remote_path

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

        Args:
            remote_path: Path to the file in storage.
            local_path: Local destination path.

        Returns:
            The local path where the file was downloaded.

        Raises:
            FileNotFoundStorageError: If remote_path doesn't exist.
        """
        source_path = self._get_full_path(remote_path)

        if not source_path.exists():
            raise FileNotFoundStorageError(remote_path)

        local_path.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(source_path, local_path)

        return local_path

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

        Args:
            remote_path: Path to check in storage.

        Returns:
            True if the file exists, False otherwise.
        """
        return self._get_full_path(remote_path).exists()

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

        Args:
            prefix: Path prefix to filter files.

        Returns:
            Sorted list of file paths matching the prefix.
        """
        if prefix:
            search_path = self._get_full_path(prefix)
            if not search_path.exists():
                return []
            base_for_relative = self._base_path
        else:
            search_path = self._base_path
            base_for_relative = self._base_path

        files: list[str] = []
        if search_path.is_file():
            files.append(str(search_path.relative_to(self._base_path)))
        elif search_path.is_dir():
            for file_path in search_path.rglob("*"):
                if file_path.is_file():
                    relative_path = file_path.relative_to(self._base_path)
                    files.append(str(relative_path).replace("\\", "/"))

        return sorted(files)

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

        Args:
            remote_path: Path to the file to delete.

        Returns:
            True if file was deleted, False if it didn't exist.
        """
        file_path = self._get_full_path(remote_path)

        if not file_path.exists():
            return False

        file_path.unlink()
        return True

    def get_url(self, remote_path: str) -> str:
        """Get a file:// URL to access a file.

        Args:
            remote_path: Path to the file in storage.

        Returns:
            file:// URL to access the file.

        Raises:
            FileNotFoundStorageError: If remote_path doesn't exist.
        """
        file_path = self._get_full_path(remote_path)

        if not file_path.exists():
            raise FileNotFoundStorageError(remote_path)

        return file_path.as_uri()

    def upload_bytes(
        self, data: bytes, remote_path: str, overwrite: bool = False
    ) -> str:
        """Upload bytes directly to storage.

        Args:
            data: Bytes to upload.
            remote_path: Destination path in storage.
            overwrite: If True, overwrite existing file.

        Returns:
            The remote path where the data was stored.
        """
        dest_path = self._get_full_path(remote_path)

        if dest_path.exists() and not overwrite:
            raise StorageError(f"File already exists: {remote_path}")

        dest_path.parent.mkdir(parents=True, exist_ok=True)
        dest_path.write_bytes(data)

        return remote_path

    def download_bytes(self, remote_path: str) -> bytes:
        """Download a file as bytes.

        Args:
            remote_path: Path to the file in storage.

        Returns:
            The file contents as bytes.

        Raises:
            FileNotFoundStorageError: If remote_path doesn't exist.
        """
        file_path = self._get_full_path(remote_path)

        if not file_path.exists():
            raise FileNotFoundStorageError(remote_path)

        return file_path.read_bytes()

    def get_presigned_url(
        self,
        remote_path: str,
        expires_in_seconds: int = 3600,
    ) -> str:
        """Get a file:// URL for local file access.

        For local storage, this returns a file:// URI.
        Note: Local file:// URLs don't actually expire.

        Args:
            remote_path: Path to the file in storage.
            expires_in_seconds: Ignored for local storage (URLs don't expire).

        Returns:
            file:// URL to access the file.

        Raises:
            FileNotFoundStorageError: If remote_path doesn't exist.
        """
        file_path = self._get_full_path(remote_path)

        if not file_path.exists():
            raise FileNotFoundStorageError(remote_path)

        return file_path.as_uri()
