# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""Shared helpers for core agent tools (file scope, binary detection, temp folder)."""

import logging
from typing import Any, Optional

logger = logging.getLogger(__name__)

_MAX_TOOL_RESULT_CHARS = 50_000

_BINARY_SIGNATURES = (b"%PDF", b"\x89PNG", b"\xff\xd8\xff", b"GIF8", b"PK\x03\x04", b"Rar!", b"\x1f\x8b")


def _resolveFileScope(fileId: str, context: dict) -> tuple:
    """Resolve featureInstanceId and mandateId for a file from context or management DB.

    Returns (featureInstanceId, mandateId) — never None, always strings.
    """
    fiId = context.get("featureInstanceId", "") or ""
    mId = context.get("mandateId", "") or ""
    if fiId and mId:
        return fiId, mId
    try:
        from modules.datamodels.datamodelFiles import FileItem
        from modules.interfaces.interfaceDbManagement import ComponentObjects
        fm = ComponentObjects().db._loadRecord(FileItem, fileId)
        if fm:
            _get = (lambda k: fm.get(k, "")) if isinstance(fm, dict) else (lambda k: getattr(fm, k, ""))
            fiId = fiId or str(_get("featureInstanceId") or "")
            mId = mId or str(_get("mandateId") or "")
    except Exception as e:
        logger.warning(f"_resolveFileScope failed for fileId={fileId}: {e}")
    return fiId, mId


def _looksLikeBinary(data: bytes, sampleSize: int = 1024) -> bool:
    """Detect binary content by checking for magic bytes and non-printable char ratio."""
    if any(data[:8].startswith(sig) for sig in _BINARY_SIGNATURES):
        return True
    sample = data[:sampleSize]
    if not sample:
        return False
    nonPrintable = sum(1 for b in sample if b < 0x09 or (0x0E <= b < 0x20 and b != 0x1B))
    return nonPrintable / len(sample) > 0.10


def _getOrCreateInstanceFolder(chatService, featureInstanceId: str, mandateId: str = "") -> Optional[str]:
    """Return the folder ID for a feature instance, creating it on first use.

    Delegates to interfaceDbManagement._ensureFeatureInstanceFolder.
    AI tools call this when saving a file without an explicit folderId
    so that instance-produced files land in a named folder automatically.
    """
    try:
        dbMgmt = chatService.interfaceDbComponent
        return dbMgmt._ensureFeatureInstanceFolder(featureInstanceId, mandateId)
    except Exception as e:
        logger.warning(f"Could not get/create instance folder for {featureInstanceId}: {e}")
        return None


def _getOrCreateTempFolder(chatService) -> Optional[str]:
    """Return the ID of the root-level 'Temp' folder, creating it if it doesn't exist."""
    try:
        allFolders = chatService.interfaceDbComponent.listFolders()
        tempFolder = next(
            (f for f in allFolders
             if f.get("name") == "Temp" and not f.get("parentId")),
            None,
        )
        if tempFolder:
            return tempFolder.get("id")
        newFolder = chatService.interfaceDbComponent.createFolder("Temp", parentId=None)
        return newFolder.get("id") if newFolder else None
    except Exception as e:
        logger.warning(f"Could not get/create Temp folder: {e}")
        return None


def _buildResolverDbFromServices(services: Any):
    """DB adapter for ConnectorResolver: load UserConnections by id.

    interfaceDbApp exposes getUserConnectionById; ConnectorResolver expects getUserConnection.
    """
    chatService = services.chat
    appIf = getattr(chatService, "interfaceDbApp", None)
    if appIf and hasattr(appIf, "getUserConnectionById"):

        class _Adapter:
            def __init__(self, app):
                self._app = app

            def getUserConnection(self, connectionId: str):
                return self._app.getUserConnectionById(connectionId)

        return _Adapter(appIf)
    return getattr(chatService, "interfaceDbComponent", None)

