"""Download tool for saving web files to local filesystem."""

from __future__ import annotations

import asyncio
import mimetypes
from functools import cache
from pathlib import Path
from typing import Annotated, Any, cast
from uuid import uuid4

from pydantic import Field
from pydantic_ai import RunContext
from ya_agent_environment import FileOperator

from ya_agent_sdk._logger import get_logger
from ya_agent_sdk.context import AgentContext
from ya_agent_sdk.toolsets.core.base import BaseTool
from ya_agent_sdk.toolsets.core.web._http_client import ForbiddenUrlError, safe_stream_request, verify_url

logger = get_logger(__name__)
_PROMPTS_DIR = Path(__file__).parent / "prompts"


@cache
def _load_instruction() -> str:
    return (_PROMPTS_DIR / "download.md").read_text()


class DownloadTool(BaseTool):
    """Download files from the web and save to local filesystem."""

    name = "download"
    description = "Download files from URLs and save to local filesystem."

    def is_available(self, ctx: RunContext[AgentContext]) -> bool:
        """Check if tool is available (requires file_operator)."""
        if ctx.deps.file_operator is None:
            logger.debug("DownloadTool unavailable: file_operator is not configured")
            return False
        return True

    async def get_instruction(self, ctx: RunContext[AgentContext]) -> str | None:
        return _load_instruction()

    async def call(
        self,
        ctx: RunContext[AgentContext],
        urls: Annotated[list[str], Field(description="List of URLs to download")],
        save_dir: Annotated[
            str,
            Field(description="Directory where files should be saved (relative path)"),
        ],
    ) -> list[dict[str, Any]]:
        """Download files from URLs and save to local directory."""
        file_operator = cast(FileOperator, ctx.deps.file_operator)

        # Ensure directory exists
        await file_operator.mkdir(save_dir, parents=True)

        # Download all URLs in parallel
        semaphore = asyncio.Semaphore(ctx.deps.tool_config.download_max_concurrency)
        tasks = [self._download_one(ctx, url, save_dir, semaphore) for url in urls]
        return await asyncio.gather(*tasks)

    async def _download_one(
        self,
        ctx: RunContext[AgentContext],
        url: str,
        save_dir: str,
        semaphore: asyncio.Semaphore,
    ) -> dict[str, Any]:
        """Download a single file."""
        file_operator = cast(FileOperator, ctx.deps.file_operator)
        skip_verification = ctx.deps.tool_config.skip_url_verification

        # Verify URL security
        if not skip_verification:
            try:
                verify_url(url)
            except ForbiddenUrlError as e:
                logger.warning(f"URL access forbidden: {url} - {e}")
                return {"success": False, "url": url, "error": f"URL access forbidden: {e}"}

        try:
            chunk_size = ctx.deps.tool_config.fetch_stream_chunk_size
            async with (
                semaphore,
                safe_stream_request(url, method="GET", timeout=60.0, skip_verification=skip_verification) as response,
            ):
                response.raise_for_status()

                content_type = response.headers.get("Content-Type", "").split(";")[0].strip()
                original_name = Path(url.split("?")[0]).name or "downloaded"

                extension = self._get_extension(content_type, original_name)
                filename = f"{uuid4().hex}{extension}"
                save_path = f"{save_dir}/{filename}"

                total_size = 0

                async def byte_stream():
                    nonlocal total_size
                    async for chunk in response.aiter_bytes(chunk_size=chunk_size):
                        total_size += len(chunk)
                        yield chunk

                await file_operator.write_bytes_stream(save_path, byte_stream())

                return {
                    "success": True,
                    "url": url,
                    "save_path": save_path,
                    "size": total_size,
                    "content_type": content_type,
                    "message": f"Downloaded to {save_path}. Use `move` tool to rename if needed.",
                }

        except ForbiddenUrlError as e:
            return {"success": False, "url": url, "error": f"Redirect forbidden: {e}"}
        except Exception:
            logger.exception(f"Failed to download {url}")
            return {"success": False, "url": url, "error": "Download failed"}

    def _get_extension(self, content_type: str, original_name: str) -> str:
        """Get file extension from content type or original name."""
        # Try to get extension from original name
        ext = Path(original_name).suffix
        if ext:
            return ext

        # Fallback to content type
        ext = mimetypes.guess_extension(content_type) or ""
        return ext
