"""WebDAV cloud provider implementation."""

import os
from pathlib import Path
from typing import Dict, Any

try:
    from webdav3.client import Client
    from webdav3.exceptions import WebDavException
    WEBDAV_AVAILABLE = True
except ImportError:
    WEBDAV_AVAILABLE = False
    # Create a dummy exception class for type checking
    class WebDavException(Exception):
        pass

from ..interface import CloudProvider


class WebDAVProvider(CloudProvider):
    """WebDAV cloud provider implementation."""

    def __init__(self, config: Dict[str, Any]):
        """
        Initialize the WebDAV provider.

        Args:
            config: Configuration dictionary with keys:
                - url: WebDAV server URL
                - username: Username for authentication
                - password: Password for authentication
                - remote_path: Base remote path for synchronization (optional)
        """
        if not WEBDAV_AVAILABLE:
            raise ImportError(
                "webdavclient3 is not installed. "
                "Install it with: pip install webdavclient3"
            )

        self.url = config.get("url", "").rstrip("/")
        self.username = config.get("username", "")
        self.password = config.get("password", "")
        self.remote_base_path = config.get("remote_path", "/ccat").rstrip("/")

        if not self.url:
            raise ValueError("WebDAV URL is required")
        if not self.username:
            raise ValueError("WebDAV username is required")
        if not self.password:
            raise ValueError("WebDAV password is required")

        self.client = Client({
            'webdav_hostname': self.url,
            'webdav_login': self.username,
            'webdav_password': self.password,
            'webdav_timeout': 30,
        })

    def test_connection(self) -> bool:
        """Test the connection to the WebDAV server."""
        try:
            return self.client.check(self.remote_base_path) or True
        except (WebDavException, ConnectionError, OSError):
            return False

    def upload(self, local_path: Path, remote_path: str) -> bool:
        """Upload a file or directory to WebDAV."""
        try:
            remote_full_path = f"{self.remote_base_path}/{remote_path}".rstrip("/")

            if local_path.is_dir():
                # Create remote directory
                self._ensure_remote_dir(remote_full_path)

                # Upload all files recursively
                for item in local_path.rglob("*"):
                    if item.is_file():
                        rel_path = item.relative_to(local_path)
                        remote_file_path = f"{remote_full_path}/{rel_path}".replace("\\", "/")
                        self._ensure_remote_dir(os.path.dirname(remote_file_path))
                        self.client.upload_sync(
                            remote_path=remote_file_path,
                            local_path=str(item)
                        )
            else:
                # Upload single file
                self._ensure_remote_dir(os.path.dirname(remote_full_path))
                self.client.upload_sync(
                    remote_path=remote_full_path,
                    local_path=str(local_path)
                )

            return True
        except (WebDavException, OSError, IOError):
            return False

    def download(self, remote_path: str, local_path: Path) -> bool:
        """Download a file or directory from WebDAV."""
        try:
            remote_full_path = f"{self.remote_base_path}/{remote_path}".rstrip("/")

            if self.client.is_dir(remote_full_path):
                # Download directory recursively
                local_path.mkdir(parents=True, exist_ok=True)
                self._download_dir(remote_full_path, local_path)
            else:
                # Download single file
                local_path.parent.mkdir(parents=True, exist_ok=True)
                self.client.download_sync(
                    remote_path=remote_full_path,
                    local_path=str(local_path)
                )

            return True
        except (WebDavException, OSError, IOError):
            return False

    def sync(self, local_path: Path, remote_path: str) -> bool:
        """
        Synchronize local directory with WebDAV.

        This performs a simple two-way sync:
        1. Upload local files that don't exist remotely
        2. Download remote files that don't exist locally
        """
        try:
            remote_full_path = f"{self.remote_base_path}/{remote_path}".rstrip("/")

            # Ensure remote directory exists
            self._ensure_remote_dir(remote_full_path)

            # Upload local files
            if local_path.exists():
                for item in local_path.rglob("*"):
                    if item.is_file():
                        rel_path = item.relative_to(local_path)
                        remote_file_path = f"{remote_full_path}/{rel_path}".replace("\\", "/")

                        # Upload if remote doesn't exist or is older
                        if not self.client.check(remote_file_path):
                            self._ensure_remote_dir(os.path.dirname(remote_file_path))
                            self.client.upload_sync(
                                remote_path=remote_file_path,
                                local_path=str(item)
                            )

            # Download remote files that don't exist locally
            if self.client.check(remote_full_path):
                self._sync_download(remote_full_path, local_path)

            return True
        except (WebDavException, OSError, IOError):
            return False

    def _ensure_remote_dir(self, remote_path: str) -> None:
        """Ensure a remote directory exists."""
        try:
            if not self.client.check(remote_path):
                self.client.mkdir(remote_path)
        except (WebDavException, OSError):
            # Directory might already exist or parent doesn't exist
            # Try creating parent directories recursively
            parts = remote_path.strip("/").split("/")
            current_path = ""
            for part in parts:
                current_path = f"{current_path}/{part}"
                try:
                    if not self.client.check(current_path):
                        self.client.mkdir(current_path)
                except (WebDavException, OSError):
                    # Ignore errors - directory may already exist
                    pass

    def _download_dir(self, remote_path: str, local_path: Path) -> None:
        """Download a directory recursively."""
        files = self.client.list(remote_path)
        for file in files:
            if file == remote_path or file.endswith("/"):
                continue

            remote_file = f"{remote_path}/{file}".replace("//", "/")
            local_file = local_path / file

            if self.client.is_dir(remote_file):
                local_file.mkdir(parents=True, exist_ok=True)
                self._download_dir(remote_file, local_file)
            else:
                self.client.download_sync(
                    remote_path=remote_file,
                    local_path=str(local_file)
                )

    def _sync_download(self, remote_path: str, local_path: Path) -> None:
        """Download files that don't exist locally."""
        try:
            files = self.client.list(remote_path)
            for file in files:
                if file == remote_path or file.endswith("/"):
                    continue

                remote_file = f"{remote_path}/{file}".replace("//", "/")
                local_file = local_path / file

                if self.client.is_dir(remote_file):
                    if not local_file.exists():
                        local_file.mkdir(parents=True, exist_ok=True)
                    self._sync_download(remote_file, local_file)
                else:
                    if not local_file.exists():
                        self.client.download_sync(
                            remote_path=remote_file,
                            local_path=str(local_file)
                        )
        except (WebDavException, OSError, IOError):
            # Continue if some files fail to sync
            pass

    @classmethod
    def get_provider_name(cls) -> str:
        """Get the name of the cloud provider."""
        return "webdav"

    @classmethod
    def get_required_config_fields(cls) -> Dict[str, str]:
        """Get the required configuration fields."""
        return {
            "url": "WebDAV server URL (e.g., https://cloud.example.com/remote.php/dav/files/username/)",
            "username": "Username for authentication",
            "password": "Password for authentication",
            "remote_path": "Remote path for synchronization (default: /ccat)",
        }
