"""File upload functionality for ArthHub MCP Server."""

# Import built-in modules
import asyncio
from pathlib import Path
from typing import Annotated
from typing import Any

# Import third-party modules
from arthub_api import Storage
from loguru import logger
from mcp.server.fastmcp import Context
from pydantic import Field

# Import local modules
from arthub_mcp_server.app import mcp
from arthub_mcp_server.config import get_open_api
from arthub_mcp_server.errors import ArthubError
from arthub_mcp_server.errors import ErrorCode


async def upload_file(
    asset_hub: str,
    remote_dir_path: str,
    local_file_path: str,
    description: str | None = None,
    same_name_override: bool = False,
    need_convert: bool = True,
    ctx: Context | None = None,
) -> dict[str, Any]:
    """Upload file or directory to ArthHub storage.

    Args:
        asset_hub: Name of the asset repository
        remote_dir_path: Remote directory path where the content will be uploaded
        local_file_path: Local file or directory path to upload
        description: Description for the uploaded content
        same_name_override: Override existing file with same name
        need_convert: Whether to convert the files
        ctx: FastMCP context

    Returns:
        dict: Response containing upload result

    Raises:
        ArthubError: If upload fails

    """
    if ctx:
        await ctx.report_progress(0.1)
        await ctx.info(f"Preparing to upload: {local_file_path}")

    try:
        # Validate file
        file_path = await _validate_file(local_file_path, ctx)

        # Get Storage instance
        if ctx:
            await ctx.report_progress(0.2)
            await ctx.info("Connecting to ArthHub...")

        storage = await _get_storage(ctx)

        # Upload file
        if ctx:
            await ctx.report_progress(0.3)
            await ctx.info(f"Uploading to {asset_hub}:{remote_dir_path}...")

        result = await _upload_to_arthub(
            storage=storage,
            asset_hub=asset_hub,
            remote_dir_path=remote_dir_path,
            file_path=file_path,
            description=description or "",
            same_name_override=same_name_override,
            need_convert=need_convert,
            ctx=ctx,
        )

        return result

    except ArthubError:
        raise
    except Exception as e:
        error_msg = f"Error uploading file: {e!s}"
        logger.error(error_msg)
        if ctx:
            await ctx.error(error_msg)
        raise ArthubError(error_msg, ErrorCode.UNKNOWN) from e


def _validate_file_sync(file_path: str | Path) -> Path:
    """Validate file or directory existence (synchronous).

    Args:
        file_path: Path to file or directory

    Returns:
        Path: Validated path

    Raises:
        ArthubError: If path is not found

    """
    # Convert to Path object if string
    if isinstance(file_path, str):
        file_path = Path(file_path)

    # Validate path exists
    if not file_path.exists():
        error_msg = f"Path not found: {file_path}"
        logger.error(error_msg)
        raise ArthubError(error_msg, ErrorCode.FILE_ERROR)

    if file_path.is_file():
        logger.info(f"File validated: {file_path}")
    elif file_path.is_dir():
        logger.info(f"Directory validated: {file_path}")
    else:
        error_msg = f"Invalid path type: {file_path}"
        logger.error(error_msg)
        raise ArthubError(error_msg, ErrorCode.FILE_ERROR)

    return file_path


async def _validate_file(file_path: str | Path, ctx: Context | None = None) -> Path:
    """Validate file existence and type.

    Args:
        file_path: Path to file
        ctx: FastMCP context

    Returns:
        Path: Validated file path

    Raises:
        ArthubError: If file is not found or not a file

    """
    try:
        return await asyncio.to_thread(_validate_file_sync, file_path)
    except ArthubError as e:
        if ctx:
            await ctx.error(str(e))
        raise


def _get_storage_sync() -> Storage:
    """Get Storage instance with authentication (synchronous).

    Returns:
        Storage: Authenticated Storage instance

    Raises:
        ArthubError: If authentication fails

    """
    try:
        open_api = get_open_api()
        storage = Storage(open_api)
        logger.info("Storage instance created")
        return storage
    except ArthubError:
        raise
    except Exception as e:
        error_msg = f"Failed to create Storage instance: {e!s}"
        logger.error(error_msg)
        raise ArthubError(error_msg, ErrorCode.CONFIG_ERROR) from e


async def _get_storage(ctx: Context | None = None) -> Storage:
    """Get Storage instance with authentication.

    Args:
        ctx: FastMCP context

    Returns:
        Storage: Authenticated Storage instance

    Raises:
        ArthubError: If authentication fails

    """
    try:
        return await asyncio.to_thread(_get_storage_sync)
    except ArthubError as e:
        if ctx:
            await ctx.error(str(e))
        raise


def _upload_to_arthub_sync(
    storage: Storage,
    asset_hub: str,
    remote_dir_path: str,
    file_path: Path,
    description: str,
    same_name_override: bool,
    need_convert: bool,
) -> dict[str, Any]:
    """Upload file to ArthHub using Storage API (synchronous).

    Args:
        storage: Storage instance
        asset_hub: Name of the asset repository
        remote_dir_path: Remote directory path
        file_path: Local file path
        description: Description
        same_name_override: Override existing file
        need_convert: Convert file

    Returns:
        dict: Upload result

    Raises:
        ArthubError: If upload fails

    """
    # Progress callback
    total_size = file_path.stat().st_size

    def progress_cb(completed: int, total: int) -> None:
        percentage = (float(completed) / float(total)) * 100.0
        logger.info(f"Upload progress: {percentage:.1f}%")

    try:
        # Call ArthHub API (synchronous)
        res = storage.upload_to_directory_by_path(
            asset_hub=asset_hub,
            remote_dir_path=remote_dir_path,
            local_path=str(file_path),
            tags_to_create=[],  # No tags
            same_name_override=same_name_override,
            need_convert=need_convert,
            description=description,
            progress_cb=progress_cb,
        )

        if not res.is_succeeded():
            error_msg = f"Upload failed: {res.error_message()}"
            logger.error(error_msg)
            raise ArthubError(error_msg, ErrorCode.API_FAILURE)

        # Parse result
        uploaded_node = res.data[0]
        uploaded_node_id = uploaded_node.id
        uploaded_node_parent_id = uploaded_node.parent_id
        uploaded_node_origin_url = getattr(uploaded_node, "origin_url", "")

        is_directory = file_path.is_dir()
        item_type = "Directory" if is_directory else "File"
        success_msg = f"{item_type} uploaded successfully: {file_path.name}"
        logger.info(success_msg)

        return {
            "status": "success",
            "message": success_msg,
            "name": file_path.name,
            "type": "directory" if is_directory else "file",
            "size": total_size if not is_directory else None,
            "node_id": uploaded_node_id,
            "parent_id": uploaded_node_parent_id,
            "origin_url": uploaded_node_origin_url,
            "asset_hub": asset_hub,
            "remote_path": remote_dir_path,
        }

    except ArthubError:
        raise
    except Exception as e:
        error_msg = f"Upload failed: {e!s}"
        logger.error(error_msg)
        raise ArthubError(error_msg, ErrorCode.API_FAILURE) from e


async def _upload_to_arthub(
    storage: Storage,
    asset_hub: str,
    remote_dir_path: str,
    file_path: Path,
    description: str,
    same_name_override: bool,
    need_convert: bool,
    ctx: Context | None = None,
) -> dict[str, Any]:
    """Upload file to ArthHub using Storage API.

    Args:
        storage: Storage instance
        asset_hub: Name of the asset repository
        remote_dir_path: Remote directory path
        file_path: Local file path
        description: Description
        same_name_override: Override existing file
        need_convert: Convert file
        ctx: FastMCP context

    Returns:
        dict: Upload result

    Raises:
        ArthubError: If upload fails

    """
    try:
        result = await asyncio.to_thread(
            _upload_to_arthub_sync,
            storage,
            asset_hub,
            remote_dir_path,
            file_path,
            description,
            same_name_override,
            need_convert,
        )

        if ctx:
            await ctx.report_progress(1.0)
            await ctx.info(result["message"])

        return result

    except ArthubError as e:
        if ctx:
            await ctx.error(str(e))
        raise


@mcp.tool(name="upload_file")
async def upload_file_mcp(
    asset_hub: Annotated[str, Field(description="Name of the asset repository")],
    remote_dir_path: Annotated[
        str,
        Field(description="Remote directory path where the content will be uploaded. Example: 'project/subdir'"),
    ],
    local_file_path: Annotated[
        str, Field(description="Local file or directory path to upload. Supports both single files and directories")
    ],
    description: Annotated[
        str | None,
        Field(description="Description for the uploaded content"),
    ] = None,
    same_name_override: Annotated[
        bool,
        Field(description="Override existing file with same name, defaults to False"),
    ] = False,
    need_convert: Annotated[
        bool,
        Field(description="Whether to convert the files, defaults to True"),
    ] = True,
) -> dict[str, Any]:
    """Upload file or directory to ArthHub storage.

    This tool supports both single file upload and batch directory upload.
    When uploading a directory, all files and subdirectories will be uploaded recursively.

    Args:
        asset_hub: Name of the asset repository
        remote_dir_path: Remote directory path where the content will be uploaded
        local_file_path: Local file or directory path to upload
        description: Description for the uploaded content
        same_name_override: Override existing file with same name
        need_convert: Whether to convert the files

    Returns:
        dict: Response with upload result

    Raises:
        ArthubError: If upload fails

    """
    return await upload_file(
        asset_hub=asset_hub,
        remote_dir_path=remote_dir_path,
        local_file_path=local_file_path,
        description=description,
        same_name_override=same_name_override,
        need_convert=need_convert,
        ctx=None,
    )

