"""share_file tool — expose a local file to the user as a download link."""

import asyncio
import shutil
from pathlib import Path
from urllib.parse import quote

from navi.config import settings
from navi.session_files import ensure_session_dir

from ._internal.base import Tool, ToolContext, ToolResult, current_session_id, current_user_role, current_user_id


def _fmt_size(n: int) -> str:
    if n < 1024:
        return f"{n} B"
    if n < 1024 ** 2:
        return f"{n / 1024:.1f} KB"
    return f"{n / 1024 ** 2:.1f} MB"


class ShareFileTool(Tool):
    name = "share_file"
    description = (
        "Copy a local file into this session's directory and return a download link — for a "
        "file the user should keep: an archive, report, export, dataset, PDF, source bundle.\n\n"
        "`path` must be ABSOLUTE; a relative one is rejected. Resolve it first with "
        "filesystem(action='info') or terminal('realpath ...').\n\n"
        "For an inline viewer card rather than a download, use content_publish, which "
        "publishes a file that is already in the session directory.\n\n"
        'How to present the link: tool_manual("share_file").'
    )
    parameters = {
        "type": "object",
        "properties": {
            "path": {
                "type": "string",
                "description": "Absolute path to the file to share",
            },
            "filename": {
                "type": "string",
                "description": (
                    "Download filename shown to the user. "
                    "Defaults to the original filename. "
                    "Use this to give the file a clean, descriptive name."
                ),
            },
        },
        "required": ["path"],
    }

    async def execute(self, params: dict, ctx: ToolContext | None = None) -> ToolResult:
        session_id = ctx.session_id if ctx else current_session_id.get()
        if not session_id:
            return ToolResult(success=False, output="No active session context.", error="no_session")

        raw_path = Path(params["path"]).expanduser()
        user_id = ctx.user_id if ctx else current_user_id.get(None)
        role = ctx.user_role if ctx else current_user_role.get()

        if user_id and role != "admin":
            sandbox = Path("user_data") / user_id
            sandbox = sandbox.expanduser().resolve()
            sandbox.mkdir(parents=True, exist_ok=True)
            if raw_path.is_absolute():
                src = raw_path.resolve()
                try:
                    src.relative_to(sandbox)
                except ValueError:
                    return ToolResult(
                        success=False,
                        output=f"Access denied: path is outside user sandbox.",
                        error="access_denied",
                    )
            else:
                src = (sandbox / raw_path).resolve()
        else:
            if not raw_path.is_absolute():
                return ToolResult(
                    success=False,
                    output=(
                        f"share_file requires an absolute path, got: {params['path']}\n"
                        "Resolve it first with filesystem info or terminal realpath, then call share_file again."
                    ),
                    error="path_not_absolute",
                )
            src = raw_path.resolve()

        if not src.exists():
            return ToolResult(success=False, output=f"File not found: {src}", error="not_found")
        if not src.is_file():
            return ToolResult(success=False, output=f"Path is not a file: {src}", error="not_a_file")

        max_bytes = settings.share_file_max_size_mb * 1024 * 1024
        src_size = src.stat().st_size
        if src_size > max_bytes:
            return ToolResult(
                success=False,
                output=(
                    f"File is too large to share: {_fmt_size(src_size)}. "
                    f"Limit: {settings.share_file_max_size_mb} MB."
                ),
                error="file_too_large",
            )

        # Determine the download filename
        download_name = Path(params.get("filename") or src.name).name or src.name

        dest_dir = ensure_session_dir(session_id)
        dest = dest_dir / download_name

        # If it's already the same file in the session dir, skip copy
        if dest.resolve() == src.resolve():
            pass
        else:
            # Avoid clobbering an existing different file with the same name
            if dest.exists():
                stem = Path(download_name).stem
                suffix = Path(download_name).suffix
                i = 1
                while dest.exists():
                    dest = dest_dir / f"{stem}_{i}{suffix}"
                    i += 1
            await asyncio.to_thread(shutil.copy2, str(src), str(dest))

        size = dest.stat().st_size
        base_url = settings.public_url.rstrip("/")
        url = f"{base_url}/api/sessions/{quote(session_id, safe='')}/files/{quote(dest.name, safe='')}"

        return ToolResult(
            success=True,
            output=(
                f"Download ready: {dest.name} ({_fmt_size(size)})\n"
                f"URL: {url}\n\n"
                f"→ Include this link in your reply: [{dest.name}]({url})"
            ),
            metadata={"url": url, "filename": dest.name, "size": size},
        )
