"""Dropbox storage plugin — adapter that satisfies the StorageProvider protocol.

Part of the local-first backup system (Mercury Wave 3). Uploads encrypted backups
to the user's own Dropbox account. User data never lives on Vastian servers.

Setup: Run `vastian setup dropbox` to complete OAuth2 flow and store tokens
in config/local/dropbox_token.json.

Requires: dropbox (optional dep).
"""

from __future__ import annotations

import json
import logging
from datetime import UTC, datetime
from pathlib import Path
from typing import Any

logger = logging.getLogger(__name__)

_TOKEN_FILE = "dropbox_token.json"
_APP_KEY_FILE = "dropbox_app_key.txt"
_VASTIAN_FOLDER = "/Vastian Backups"


class DropboxAdapter:
    """Adapter that satisfies the StorageProvider protocol for Dropbox.

    Protocol satisfaction: isinstance(DropboxAdapter(...), StorageProvider) is True.
    """

    def __init__(self, config_dir: str | Path = "config/local") -> None:
        self._config_dir = Path(config_dir)
        self._token_path = self._config_dir / _TOKEN_FILE
        self._dbx: Any = None

    def _get_access_token(self) -> str:
        """Get access token from env, then token file."""
        from vastian.config import get_settings

        token = (get_settings().dropbox_access_token or "").strip()
        if token:
            return token
        if self._token_path.exists():
            token_data = json.loads(self._token_path.read_text(encoding="utf-8"))
            return token_data.get("access_token", "")
        return ""

    def _ensure_client(self) -> None:
        """Lazily initialize the Dropbox client."""
        if self._dbx is not None:
            return
        if not self.is_authenticated():
            raise RuntimeError(
                "Dropbox not authenticated. Set DROPBOX_ACCESS_TOKEN in .env, "
                "or run `vastian setup dropbox` to complete the OAuth2 flow."
            )
        try:
            import dropbox

            token = self._get_access_token()
            self._dbx = dropbox.Dropbox(token)
        except ImportError as err:
            raise RuntimeError(
                "Dropbox dependency not installed. Run: pip install dropbox"
            ) from err

    async def upload(self, local_path: str, remote_path: str) -> dict:
        """Upload a local file to Dropbox."""
        self._ensure_client()
        import dropbox

        local = Path(local_path)
        if not local.exists():
            raise FileNotFoundError(f"Local file not found: {local_path}")

        full_remote = f"{_VASTIAN_FOLDER}/{remote_path}"
        with open(local, "rb") as f:
            result = self._dbx.files_upload(
                f.read(),
                full_remote,
                mode=dropbox.files.WriteMode.overwrite,
            )

        return {
            "remote_path": remote_path,
            "remote_id": result.id,
            "size": local.stat().st_size,
            "timestamp": datetime.now(UTC).isoformat(),
        }

    async def download(self, remote_path: str, local_path: str) -> dict:
        """Download a file from Dropbox to a local path."""
        self._ensure_client()

        full_remote = f"{_VASTIAN_FOLDER}/{remote_path}"
        local = Path(local_path)
        local.parent.mkdir(parents=True, exist_ok=True)

        metadata, response = self._dbx.files_download(full_remote)
        local.write_bytes(response.content)

        return {
            "local_path": str(local),
            "size": local.stat().st_size,
            "timestamp": datetime.now(UTC).isoformat(),
        }

    async def list_files(self, prefix: str = "") -> list[dict]:
        """List files in the Vastian Backups folder on Dropbox."""
        self._ensure_client()

        search_path = _VASTIAN_FOLDER
        if prefix:
            search_path = f"{_VASTIAN_FOLDER}/{prefix}"

        try:
            result = self._dbx.files_list_folder(search_path)
        except Exception:
            return []

        files = []
        for entry in result.entries:
            if hasattr(entry, "size"):
                files.append(
                    {
                        "remote_path": entry.name,
                        "size": entry.size,
                        "modified": entry.server_modified.isoformat()
                        if hasattr(entry, "server_modified")
                        else "",
                        "remote_id": entry.id,
                    }
                )
        return files

    async def delete(self, remote_path: str) -> bool:
        """Delete a file from Dropbox."""
        self._ensure_client()
        full_remote = f"{_VASTIAN_FOLDER}/{remote_path}"
        try:
            self._dbx.files_delete_v2(full_remote)
            return True
        except Exception:
            return False

    def get_auth_url(self) -> str:
        """Get the OAuth2 authorization URL for Dropbox."""
        try:
            import dropbox

            app_key_path = self._config_dir / _APP_KEY_FILE
            if not app_key_path.exists():
                return (
                    "No Dropbox app key found. Create a Dropbox app at "
                    "https://www.dropbox.com/developers/apps and save the "
                    f"app key to {app_key_path}"
                )
            app_key = app_key_path.read_text(encoding="utf-8").strip()
            flow = dropbox.DropboxOAuth2FlowNoRedirect(app_key, use_pkce=True)
            return flow.start()
        except ImportError:
            return "Dropbox dependency not installed. Run: pip install dropbox"

    def is_authenticated(self) -> bool:
        """Check if valid Dropbox credentials exist (env or token file)."""
        from vastian.config import get_settings

        if (get_settings().dropbox_access_token or "").strip():
            return True
        return self._token_path.exists()


def register() -> None:
    """Register the Dropbox storage plugin with the loader."""
    from vastian.loader import register as loader_register

    loader_register("dropbox_storage", DropboxAdapter)
