"""Box 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 Box account. User data never lives on Vastian servers.

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

Requires: boxsdk (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 = "box_token.json"
_CONFIG_FILE = "box_config.json"
_VASTIAN_FOLDER = "Vastian Backups"


class BoxAdapter:
    """Adapter that satisfies the StorageProvider protocol for Box.

    Protocol satisfaction: isinstance(BoxAdapter(...), 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._config_path = self._config_dir / _CONFIG_FILE
        self._client: Any = None
        self._folder_id: str | None = None

    def _ensure_client(self) -> None:
        """Lazily initialize the Box client."""
        if self._client is not None:
            return
        if not self.is_authenticated():
            raise RuntimeError(
                "Box not authenticated. Run `vastian setup box` to complete the OAuth2 flow."
            )
        try:
            from boxsdk import Client, OAuth2

            token_data = json.loads(self._token_path.read_text(encoding="utf-8"))
            config_data = (
                json.loads(self._config_path.read_text(encoding="utf-8"))
                if self._config_path.exists()
                else {}
            )

            oauth = OAuth2(
                client_id=config_data.get("client_id", ""),
                client_secret=config_data.get("client_secret", ""),
                access_token=token_data.get("access_token", ""),
            )
            self._client = Client(oauth)
        except ImportError as err:
            raise RuntimeError("Box dependency not installed. Run: pip install boxsdk") from err

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

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

        folder = self._client.folder(self._get_folder_id())
        uploaded = folder.upload(str(local), file_name=remote_path)

        return {
            "remote_path": remote_path,
            "remote_id": uploaded.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 Box to a local path."""
        self._ensure_client()

        file_id = self._find_file_id(remote_path)
        if not file_id:
            raise FileNotFoundError(f"Remote file not found: {remote_path}")

        local = Path(local_path)
        local.parent.mkdir(parents=True, exist_ok=True)

        with open(local, "wb") as f:
            self._client.file(file_id).download_to(f)

        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 Box."""
        self._ensure_client()

        folder = self._client.folder(self._get_folder_id())
        items = folder.get_items(limit=1000)

        files = []
        for item in items:
            if item.type == "file":
                if prefix and not item.name.startswith(prefix):
                    continue
                files.append(
                    {
                        "remote_path": item.name,
                        "size": item.size or 0,
                        "modified": "",
                        "remote_id": item.id,
                    }
                )
        return files

    async def delete(self, remote_path: str) -> bool:
        """Delete a file from Box."""
        self._ensure_client()
        file_id = self._find_file_id(remote_path)
        if not file_id:
            return False
        try:
            self._client.file(file_id).delete()
            return True
        except Exception:
            return False

    def get_auth_url(self) -> str:
        """Get the OAuth2 authorization URL for Box."""
        try:
            from boxsdk import OAuth2

            if not self._config_path.exists():
                return (
                    "No Box config found. Create a Box app at "
                    "https://app.box.com/developers/console and save client_id "
                    f"and client_secret to {self._config_path}"
                )
            config_data = json.loads(self._config_path.read_text(encoding="utf-8"))
            oauth = OAuth2(
                client_id=config_data.get("client_id", ""),
                client_secret=config_data.get("client_secret", ""),
            )
            auth_url, _ = oauth.get_authorization_url("https://localhost")
            return auth_url
        except ImportError:
            return "Box dependency not installed. Run: pip install boxsdk"

    def is_authenticated(self) -> bool:
        """Check if valid Box credentials exist."""
        return self._token_path.exists()

    def _get_folder_id(self) -> str:
        """Get or create the 'Vastian Backups' folder on Box."""
        if self._folder_id:
            return self._folder_id

        root = self._client.folder("0")
        items = root.get_items(limit=1000)
        for item in items:
            if item.type == "folder" and item.name == _VASTIAN_FOLDER:
                self._folder_id = item.id
                return item.id

        created = root.create_subfolder(_VASTIAN_FOLDER)
        self._folder_id = created.id
        return created.id

    def _find_file_id(self, filename: str) -> str | None:
        """Find a file ID by name in the Vastian folder."""
        folder = self._client.folder(self._get_folder_id())
        items = folder.get_items(limit=1000)
        for item in items:
            if item.type == "file" and item.name == filename:
                return item.id
        return None


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

    loader_register("box_storage", BoxAdapter)
