# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Google Drive Skill - Clio, Muse of History

Clio was the Muse who recorded and preserved the great deeds of history.
She keeps your archives organized and accessible.

Search, read, and manage Google Drive files.
Uses same OAuth flow as Calendar skill.
"""

import logging
from datetime import datetime
from pathlib import Path

logger = logging.getLogger(__name__)


REQUEST_TIMEOUT = 30  # seconds for Google API calls

# Storage paths - use centralized paths


def _get_data_dir():
    """Get data directory (tenant-scoped in Reflection, default in standalone)."""
    try:
        from familiar.core import paths

        return paths.DATA_DIR
    except ImportError:
        return Path.home() / ".familiar" / "data"


try:
    from familiar.core.paths import DATA_DIR, DOWNLOADS_DIR, ensure_dir
except ImportError:
    DATA_DIR = _get_data_dir()
    DOWNLOADS_DIR = DATA_DIR / "downloads"

    def ensure_dir(path):
        path.mkdir(parents=True, exist_ok=True)
        return path


CREDENTIALS_FILE = DATA_DIR / "google_credentials.json"
TOKEN_FILE = DATA_DIR / "google_token_drive.json"

SCOPES = [
    "https://www.googleapis.com/auth/drive.readonly",
    "https://www.googleapis.com/auth/drive.file",
]

ensure_dir(DOWNLOADS_DIR)


def _get_drive_service():
    """Get authenticated Google Drive service."""
    try:
        from google.auth.transport.requests import Request
        from google.oauth2.credentials import Credentials
        from google_auth_oauthlib.flow import InstalledAppFlow
        from googleapiclient.discovery import build
    except ImportError:
        from familiar.core.deps import GOOGLE_PACKAGES, ensure_packages

        ok, _ = ensure_packages(GOOGLE_PACKAGES)
        if ok:
            from google.auth.transport.requests import Request
            from google.oauth2.credentials import Credentials
            from google_auth_oauthlib.flow import InstalledAppFlow
            from googleapiclient.discovery import build
        else:
            raise RuntimeError("Google API setup failed. Check server logs.")

    if not CREDENTIALS_FILE.exists():
        raise RuntimeError(f"Google credentials not found at {CREDENTIALS_FILE}")

    creds = None

    if TOKEN_FILE.exists():
        creds = Credentials.from_authorized_user_file(str(TOKEN_FILE), SCOPES)

    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            try:
                creds.refresh(Request())
            except Exception as e:
                # Refresh token revoked/expired — remove stale token so re-auth can proceed
                logger.warning("Drive token refresh failed: %s — removing stale token", e)
                TOKEN_FILE.unlink(missing_ok=True)
                raise RuntimeError(
                    "Google Drive token expired or revoked. "
                    "Run /connect google auth drive to re-authorize."
                ) from e
        else:
            flow = InstalledAppFlow.from_client_secrets_file(str(CREDENTIALS_FILE), SCOPES)
            creds = flow.run_local_server(port=0)

        TOKEN_FILE.write_text(creds.to_json())

    return build("drive", "v3", credentials=creds)


def _format_size(size_bytes):
    """Format file size."""
    if not size_bytes:
        return ""
    size = int(size_bytes)
    for unit in ["B", "KB", "MB", "GB"]:
        if size < 1024:
            return f"{size:.1f}{unit}"
        size /= 1024
    return f"{size:.1f}TB"


def _get_mime_icon(mime_type):
    """Get icon for mime type."""
    if not mime_type:
        return "📄"
    if "folder" in mime_type:
        return "📁"
    if "document" in mime_type:
        return "📝"
    if "spreadsheet" in mime_type:
        return "📊"
    if "presentation" in mime_type:
        return "📽️"
    if "pdf" in mime_type:
        return "📕"
    if "image" in mime_type:
        return "🖼️"
    return "📄"


def search_files(data: dict) -> str:
    """Search for files in Drive."""
    try:
        service = _get_drive_service()
    except Exception as e:
        logger.error(f"Google Drive operation failed: {e}")
        return "❌ Google Drive operation failed. Check OAuth credentials."

    query = data.get("query", "")
    file_type = data.get("type", "")  # doc, sheet, pdf, folder
    limit = data.get("limit", 10)

    if not query:
        return "Please provide a search query"

    # Build search query
    search_parts = [f"name contains '{query}'"]

    if file_type:
        type_map = {
            "doc": "application/vnd.google-apps.document",
            "document": "application/vnd.google-apps.document",
            "sheet": "application/vnd.google-apps.spreadsheet",
            "spreadsheet": "application/vnd.google-apps.spreadsheet",
            "slides": "application/vnd.google-apps.presentation",
            "presentation": "application/vnd.google-apps.presentation",
            "pdf": "application/pdf",
            "folder": "application/vnd.google-apps.folder",
        }
        if file_type.lower() in type_map:
            search_parts.append(f"mimeType='{type_map[file_type.lower()]}'")

    search_parts.append("trashed=false")
    search_query = " and ".join(search_parts)

    try:
        results = (
            service.files()
            .list(
                q=search_query,
                pageSize=limit,
                fields="files(id, name, mimeType, size, modifiedTime, webViewLink)",
            )
            .execute()
        )

        files = results.get("files", [])

        if not files:
            return f"No files found matching '{query}'"

        lines = [f"🔍 Found {len(files)} files:\n"]

        for f in files:
            icon = _get_mime_icon(f.get("mimeType"))
            size = _format_size(f.get("size"))
            mod_time = f.get("modifiedTime", "")[:10]

            lines.append(f"{icon} {f['name']}")
            lines.append(f"   ID: {f['id']}")
            if size:
                lines.append(f"   Size: {size} | Modified: {mod_time}")

        return "\n".join(lines)

    except Exception as e:
        logger.error(f"Drive search failed: {e}")
        return "❌ Drive search failed. Check connection."


def list_folder(data: dict) -> str:
    """List contents of a folder."""
    try:
        service = _get_drive_service()
    except Exception as e:
        logger.error(f"Google Drive operation failed: {e}")
        return "❌ Google Drive operation failed. Check OAuth credentials."

    folder_id = data.get("folder_id", "root")
    folder_name = data.get("folder_name", "")

    # If folder name provided, search for it first
    if folder_name and folder_id == "root":
        try:
            search = (
                service.files()
                .list(
                    q=f"name='{folder_name}' and mimeType='application/vnd.google-apps.folder' and trashed=false",
                    fields="files(id, name)",
                )
                .execute()
            )

            folders = search.get("files", [])
            if folders:
                folder_id = folders[0]["id"]
            else:
                return f"Folder '{folder_name}' not found"
        except Exception as e:
            logger.warning(f"Folder search failed: {e}")

    try:
        results = (
            service.files()
            .list(
                q=f"'{folder_id}' in parents and trashed=false",
                pageSize=30,
                fields="files(id, name, mimeType, size, modifiedTime)",
                orderBy="folder,name",
            )
            .execute()
        )

        files = results.get("files", [])

        if not files:
            return "📁 Folder is empty"

        lines = [f"📁 Folder contents ({len(files)} items):\n"]

        for f in files:
            icon = _get_mime_icon(f.get("mimeType"))
            size = _format_size(f.get("size"))

            lines.append(f"{icon} {f['name']}" + (f" ({size})" if size else ""))

        return "\n".join(lines)

    except Exception as e:
        logger.error(f"Google Drive operation failed: {e}")
        return "❌ Google Drive operation failed."


def read_doc(data: dict) -> str:
    """Read content of a Google Doc."""
    try:
        service = _get_drive_service()
    except Exception as e:
        logger.error(f"Google Drive operation failed: {e}")
        return "❌ Google Drive operation failed. Check OAuth credentials."

    file_id = data.get("file_id", "")
    file_name = data.get("file_name", "")

    # Search by name if needed
    if file_name and not file_id:
        try:
            search = (
                service.files()
                .list(
                    q=f"name contains '{file_name}' and trashed=false",
                    fields="files(id, name, mimeType)",
                )
                .execute()
            )

            files = search.get("files", [])
            if files:
                file_id = files[0]["id"]
            else:
                return f"File '{file_name}' not found"
        except Exception as e:
            return f"Could not find file '{file_name}': {e}"

    if not file_id:
        return "Please provide file_id or file_name"

    try:
        # Get file metadata
        file_meta = service.files().get(fileId=file_id, fields="name, mimeType").execute()
        mime_type = file_meta.get("mimeType", "")
        name = file_meta.get("name", "")

        # Export Google Docs as plain text
        if "google-apps.document" in mime_type:
            content = service.files().export(fileId=file_id, mimeType="text/plain").execute()

            text = content.decode("utf-8")
            if len(text) > 8000:
                text = text[:8000] + "\n\n... (truncated)"

            return f"📝 {name}\n\n{text}"

        # Export Google Sheets as CSV
        elif "google-apps.spreadsheet" in mime_type:
            content = service.files().export(fileId=file_id, mimeType="text/csv").execute()

            text = content.decode("utf-8")
            lines = text.split("\n")[:50]  # First 50 rows

            return f"📊 {name}\n\n" + "\n".join(lines)

        else:
            return f"Cannot read this file type directly. Use download_file to download it.\nFile: {name}\nType: {mime_type}"

    except Exception as e:
        logger.error(f"Drive read failed: {e}")
        return "❌ Failed to read file from Drive. Check permissions."


def download_file(data: dict) -> str:
    """Download a file from Drive."""
    try:
        service = _get_drive_service()
        from googleapiclient.http import MediaIoBaseDownload
    except Exception as e:
        logger.error(f"Google Drive operation failed: {e}")
        return "❌ Google Drive operation failed. Check OAuth credentials."

    file_id = data.get("file_id", "")
    file_name = data.get("file_name", "")

    if file_name and not file_id:
        try:
            search = (
                service.files()
                .list(q=f"name contains '{file_name}' and trashed=false", fields="files(id, name)")
                .execute()
            )

            files = search.get("files", [])
            if files:
                file_id = files[0]["id"]
                file_name = files[0]["name"]
        except Exception as e:
            logger.warning(f"File search failed: {e}")

    if not file_id:
        return "Please provide file_id or file_name"

    try:
        # Get file metadata
        file_meta = service.files().get(fileId=file_id, fields="name, mimeType, size").execute()
        name = file_meta.get("name", "download")
        mime_type = file_meta.get("mimeType", "")

        # Handle Google Docs formats
        export_map = {
            "application/vnd.google-apps.document": ("application/pdf", ".pdf"),
            "application/vnd.google-apps.spreadsheet": (
                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                ".xlsx",
            ),
            "application/vnd.google-apps.presentation": ("application/pdf", ".pdf"),
        }

        if mime_type in export_map:
            export_mime, ext = export_map[mime_type]
            request = service.files().export_media(fileId=file_id, mimeType=export_mime)
            if not name.endswith(ext):
                name += ext
        else:
            request = service.files().get_media(fileId=file_id)

        filepath = DOWNLOADS_DIR / name

        with open(filepath, "wb") as f:
            downloader = MediaIoBaseDownload(f, request)
            done = False
            while not done:
                status, done = downloader.next_chunk()

        return f"✅ Downloaded: {filepath}"

    except Exception as e:
        logger.error(f"Drive download failed: {e}")
        return "❌ Failed to download file from Drive."


def recent_files(data: dict) -> str:
    """Get recently modified files."""
    try:
        service = _get_drive_service()
    except Exception as e:
        logger.error(f"Google Drive operation failed: {e}")
        return "❌ Google Drive operation failed. Check OAuth credentials."

    limit = data.get("limit", 10)

    try:
        results = (
            service.files()
            .list(
                pageSize=limit,
                fields="files(id, name, mimeType, modifiedTime, size)",
                orderBy="modifiedTime desc",
                q="trashed=false",
            )
            .execute()
        )

        files = results.get("files", [])

        if not files:
            return "No recent files found"

        lines = ["📂 Recent files:\n"]

        for f in files:
            icon = _get_mime_icon(f.get("mimeType"))
            mod_time = f.get("modifiedTime", "")[:16].replace("T", " ")
            lines.append(f"{icon} {f['name']}")
            lines.append(f"   Modified: {mod_time}")

        return "\n".join(lines)

    except Exception as e:
        logger.error(f"Google Drive operation failed: {e}")
        return "❌ Google Drive operation failed."


# ══════════════════════════════════════════════════════════════════════════════
# WRITE OPERATIONS
# ══════════════════════════════════════════════════════════════════════════════


def _get_docs_service():
    """Get authenticated Google Docs API service (shares Drive token/scopes)."""
    try:
        from google.auth.transport.requests import Request
        from google.oauth2.credentials import Credentials
        from googleapiclient.discovery import build
    except ImportError:
        from familiar.core.deps import GOOGLE_PACKAGES, ensure_packages

        ok, _ = ensure_packages(GOOGLE_PACKAGES)
        if ok:
            from google.auth.transport.requests import Request
            from google.oauth2.credentials import Credentials
            from googleapiclient.discovery import build
        else:
            raise RuntimeError("Google API setup failed. Check server logs.")

    if not TOKEN_FILE.exists():
        raise RuntimeError("Drive not authorized. Run /connect google auth drive first.")

    creds = Credentials.from_authorized_user_file(str(TOKEN_FILE), SCOPES)
    if not creds.valid:
        if creds.expired and creds.refresh_token:
            try:
                creds.refresh(Request())
            except Exception as e:
                logger.warning("Drive token refresh failed: %s", e)
                TOKEN_FILE.unlink(missing_ok=True)
                raise RuntimeError(
                    "Drive token expired or revoked. Run /connect google auth drive."
                ) from e
            TOKEN_FILE.write_text(creds.to_json())
        else:
            raise RuntimeError("Drive token expired. Run /connect google auth drive.")

    return build("docs", "v1", credentials=creds)


def _get_sheets_service():
    """Get authenticated Google Sheets API service (shares Drive token/scopes)."""
    try:
        from google.auth.transport.requests import Request
        from google.oauth2.credentials import Credentials
        from googleapiclient.discovery import build
    except ImportError:
        raise RuntimeError("Google API libraries not installed.")

    if not TOKEN_FILE.exists():
        raise RuntimeError("Drive not authorized. Run /connect google auth drive first.")

    creds = Credentials.from_authorized_user_file(str(TOKEN_FILE), SCOPES)
    if not creds.valid:
        if creds.expired and creds.refresh_token:
            try:
                creds.refresh(Request())
            except Exception as e:
                logger.warning("Drive token refresh failed: %s", e)
                TOKEN_FILE.unlink(missing_ok=True)
                raise RuntimeError(
                    "Drive token expired or revoked. Run /connect google auth drive."
                ) from e
            TOKEN_FILE.write_text(creds.to_json())
        else:
            raise RuntimeError("Drive token expired. Run /connect google auth drive.")

    return build("sheets", "v4", credentials=creds)


def _resolve_folder_id(service, folder_name: str) -> str | None:
    """Resolve a folder name to its Drive ID. Returns None if not found."""
    if not folder_name:
        return None
    try:
        res = (
            service.files()
            .list(
                q=f"name='{folder_name}' and mimeType='application/vnd.google-apps.folder' and trashed=false",
                fields="files(id, name)",
                pageSize=1,
            )
            .execute()
        )
        files = res.get("files", [])
        return files[0]["id"] if files else None
    except Exception:
        return None


def _text_to_doc_requests(text: str) -> list:
    """
    Convert plain text into Docs API batchUpdate requests.
    Handles headings (lines starting with # / ## / ###) and plain paragraphs.
    Returns a list of insertText + updateParagraphStyle requests.
    """
    requests = []
    # Insert all text first (index 1 = after document start)
    # We build from the end so indices stay valid, but it's easier
    # to insert at index 1 repeatedly — each insert pushes previous content right.
    # So we reverse the lines and insert each at position 1.
    lines = text.split("\n")

    insert_index = 1
    for i, line in enumerate(lines):
        is_last = i == len(lines) - 1
        content = line + ("" if is_last else "\n")

        # Detect heading level
        heading_style = "NORMAL_TEXT"
        stripped = line.lstrip("#").strip()
        if line.startswith("### "):
            heading_style = "HEADING_3"
            content = content.replace(line, stripped, 1)
        elif line.startswith("## "):
            heading_style = "HEADING_2"
            content = content.replace(line, stripped, 1)
        elif line.startswith("# "):
            heading_style = "HEADING_1"
            content = content.replace(line, stripped, 1)

        if not content:
            content = "\n"

        end_index = insert_index + len(content)

        requests.append({"insertText": {"location": {"index": insert_index}, "text": content}})

        if heading_style != "NORMAL_TEXT":
            requests.append(
                {
                    "updateParagraphStyle": {
                        "range": {"startIndex": insert_index, "endIndex": end_index},
                        "paragraphStyle": {"namedStyleType": heading_style},
                        "fields": "namedStyleType",
                    }
                }
            )

        insert_index = end_index

    return requests


def create_doc(data: dict) -> str:
    """
    Create a new Google Doc with title and content.

    Supports markdown-style headings (# H1, ## H2, ### H3).
    The doc is created in the app's Drive space (drive.file scope).
    Optionally places it in a named folder.
    Returns the file ID and share link.
    """
    title = data.get("title", "").strip()
    content = data.get("content", "").strip()
    folder = data.get("folder", "").strip()

    if not title:
        return "Please provide a title for the document."

    if not data.get("_confirmed"):
        from familiar.core.confirmations import drive_create_preview, needs_confirmation

        wc = len(content.split()) if content else 0
        return needs_confirmation(
            "drive_create_doc", data, drive_create_preview(title, wc, folder), risk="low"
        )

    try:
        drive_svc = _get_drive_service()
        docs_svc = _get_docs_service()
    except Exception as e:
        logger.error(f"Drive/Docs service init failed: {e}")
        logger.error(f"Drive not available: {e}")
        return "❌ Google Drive not available. Check OAuth credentials and run /connect google auth drive."

    try:
        # Step 1: Create empty Doc via Docs API
        doc = docs_svc.documents().create(body={"title": title}).execute()
        doc_id = doc["documentId"]

        # Step 2: Populate content if provided
        if content:
            requests = _text_to_doc_requests(content)
            if requests:
                docs_svc.documents().batchUpdate(
                    documentId=doc_id, body={"requests": requests}
                ).execute()

        # Step 3: Move to folder if specified
        if folder:
            folder_id = _resolve_folder_id(drive_svc, folder)
            if folder_id:
                # Add to folder (keep in root too until we explicitly remove)
                file_meta = drive_svc.files().get(fileId=doc_id, fields="parents").execute()
                previous_parents = ",".join(file_meta.get("parents", []))
                drive_svc.files().update(
                    fileId=doc_id,
                    addParents=folder_id,
                    removeParents=previous_parents,
                    fields="id, parents",
                ).execute()
            else:
                logger.warning(f"Folder '{folder}' not found — doc saved to root Drive.")

        # Step 4: Get share link
        file_info = drive_svc.files().get(fileId=doc_id, fields="webViewLink, name").execute()
        link = file_info.get("webViewLink", f"https://docs.google.com/document/d/{doc_id}/edit")

        location = f" in '{folder}'" if folder else ""
        content_note = f" ({len(content.split())} words)" if content else " (empty)"

        return f"✅ Created: **{title}**{location}{content_note}\n   ID: {doc_id}\n   Link: {link}"

    except Exception as e:
        logger.error(f"Doc creation failed: {e}")
        logger.error(f"Doc creation failed: {e}")
        return "❌ Failed to create document. Check Drive permissions and try again."


def append_doc(data: dict) -> str:
    """
    Append text to an existing Google Doc (by name or ID).

    Adds content at the end of the document, preceded by a separator line
    if append_separator=true (default). Useful for accumulating meeting
    minutes, adding sections to grant reports, etc.

    Note: Only works on docs created by this app (drive.file scope).
    For docs created elsewhere, the owner must share edit access.
    """
    file_id = data.get("file_id", "").strip()
    file_name = data.get("file_name", "").strip()
    content = data.get("content", "").strip()

    # Phase 4: resolve "last" or missing file_id from session context
    if not file_id and not file_name:
        _sctx = (data.get("_context") or {}).get("session")
        _lid = _sctx.session_context.get("last_doc_id") if _sctx else None
        _ltitle = _sctx.session_context.get("last_doc_title", "") if _sctx else ""
        if _lid:
            file_id = _lid
            file_name = _ltitle
    separator = data.get("append_separator", True)

    if not content:
        return "Please provide content to append."

    if not data.get("_confirmed"):
        from familiar.core.confirmations import drive_append_preview, needs_confirmation

        wc = len(content.split())
        name = file_name or file_id or "document"
        return needs_confirmation(
            "drive_append_doc", data, drive_append_preview(name, wc), risk="low"
        )

    try:
        drive_svc = _get_drive_service()
        docs_svc = _get_docs_service()
    except Exception as e:
        logger.error(f"Drive not available: {e}")
        return "❌ Google Drive not available. Check OAuth credentials and run /connect google auth drive."

    # Resolve name → ID
    if file_name and not file_id:
        try:
            res = (
                drive_svc.files()
                .list(
                    q=f"name contains '{file_name}' and mimeType='application/vnd.google-apps.document' and trashed=false",
                    fields="files(id, name)",
                    pageSize=1,
                )
                .execute()
            )
            files = res.get("files", [])
            if not files:
                return f"Document '{file_name}' not found in Drive."
            file_id = files[0]["id"]
            file_name = files[0]["name"]
        except Exception as e:
            logger.error(f"Doc lookup failed: {e}")
        return "❌ Could not find the document in Drive. Check the name and try again."

    if not file_id:
        return "Please provide file_id or file_name."

    try:
        # Get current document to find end index
        doc = docs_svc.documents().get(documentId=file_id).execute()
        body = doc.get("body", {})
        content_elements = body.get("content", [])

        # Find the last character index (endIndex of last element - 1 for the final \n)
        end_index = 1
        for elem in content_elements:
            ei = elem.get("endIndex", 1)
            if ei > end_index:
                end_index = ei
        # Insert before the final newline that Drive always keeps
        insert_at = max(1, end_index - 1)

        requests = []

        # Optional separator
        if separator:
            sep_text = f"\n\n{'─' * 40}\n{datetime.now().strftime('%Y-%m-%d %H:%M')}\n\n"
            requests.append({"insertText": {"location": {"index": insert_at}, "text": sep_text}})
            insert_at += len(sep_text)

        # Append content lines
        append_text = content + "\n"
        requests.append({"insertText": {"location": {"index": insert_at}, "text": append_text}})

        docs_svc.documents().batchUpdate(documentId=file_id, body={"requests": requests}).execute()

        doc_title = doc.get("title", file_name or file_id)
        link = f"https://docs.google.com/document/d/{file_id}/edit"
        return (
            f"✅ Appended to **{doc_title}**\n"
            f"   Added: {len(content.split())} words\n"
            f"   Link: {link}"
        )

    except Exception as e:
        logger.error(f"Doc append failed: {e}")
        if "insufficientPermissions" in str(e) or "forbidden" in str(e).lower():
            return (
                "❌ Permission denied on this document.\n"
                "   This usually means the doc was created outside Familiar.\n"
                "   Ask the doc owner to share edit access, or create a new doc with drive_create_doc."
            )
        logger.error(f"Doc append failed: {e}")
        return (
            "❌ Failed to append to document. The doc may be read-only or in an unsupported format."
        )


def update_doc(data: dict) -> str:
    """
    Replace the full content of a Google Doc (by name or ID).

    Clears the existing body and writes new content. Supports
    markdown-style headings. Use append_doc to add without erasing.

    Note: Only works on docs created by this app (drive.file scope).
    """
    file_id = data.get("file_id", "").strip()
    file_name = data.get("file_name", "").strip()
    content = data.get("content", "").strip()

    # Phase 4: resolve "last" or missing file_id from session context
    if not file_id and not file_name:
        _sctx = (data.get("_context") or {}).get("session")
        _lid = _sctx.session_context.get("last_doc_id") if _sctx else None
        _ltitle = _sctx.session_context.get("last_doc_title", "") if _sctx else ""
        if _lid:
            file_id = _lid
            file_name = _ltitle

    if not content:
        return "Please provide content to write."

    if not data.get("_confirmed"):
        from familiar.core.confirmations import drive_update_preview, needs_confirmation

        wc = len(content.split())
        name = file_name or file_id or "document"
        return needs_confirmation(
            "drive_update_doc", data, drive_update_preview(name, wc), risk="medium"
        )

    try:
        drive_svc = _get_drive_service()
        docs_svc = _get_docs_service()
    except Exception as e:
        logger.error(f"Drive not available: {e}")
        return "❌ Google Drive not available. Check OAuth credentials and run /connect google auth drive."

    # Resolve name → ID
    if file_name and not file_id:
        try:
            res = (
                drive_svc.files()
                .list(
                    q=f"name contains '{file_name}' and mimeType='application/vnd.google-apps.document' and trashed=false",
                    fields="files(id, name)",
                    pageSize=1,
                )
                .execute()
            )
            files = res.get("files", [])
            if not files:
                return f"Document '{file_name}' not found."
            file_id = files[0]["id"]
            file_name = files[0]["name"]
        except Exception as e:
            logger.error(f"Doc lookup failed: {e}")
        return "❌ Could not find the document in Drive. Check the name and try again."

    if not file_id:
        return "Please provide file_id or file_name."

    try:
        # Get current end index to delete all existing content
        doc = docs_svc.documents().get(documentId=file_id).execute()
        body_content = doc.get("body", {}).get("content", [])
        end_index = 1
        for elem in body_content:
            ei = elem.get("endIndex", 1)
            if ei > end_index:
                end_index = ei

        requests = []

        # Delete existing content (leave the mandatory trailing newline at index 1)
        if end_index > 2:
            requests.append(
                {"deleteContentRange": {"range": {"startIndex": 1, "endIndex": end_index - 1}}}
            )

        # Insert new content
        requests.extend(_text_to_doc_requests(content))

        docs_svc.documents().batchUpdate(documentId=file_id, body={"requests": requests}).execute()

        doc_title = doc.get("title", file_name or file_id)
        link = f"https://docs.google.com/document/d/{file_id}/edit"
        return (
            f"✅ Updated: **{doc_title}**\n"
            f"   Content: {len(content.split())} words\n"
            f"   Link: {link}"
        )

    except Exception as e:
        logger.error(f"Doc update failed: {e}")
        if "insufficientPermissions" in str(e) or "forbidden" in str(e).lower():
            return (
                "❌ Permission denied. Doc may have been created outside Familiar.\n"
                "   Ask the owner to share edit access, or create a new doc."
            )
        logger.error(f"Doc update failed: {e}")
        return "❌ Failed to update document. The doc may be read-only or in an unsupported format."


def create_sheet(data: dict) -> str:
    """
    Create a new Google Sheet with a title, headers, and optional data rows.

    Headers is a list of column names. Rows is a list of lists (one per row).
    Optionally places the sheet in a named folder.
    Returns file ID and share link.
    """
    title = data.get("title", "").strip()
    headers = data.get("headers", [])
    rows = data.get("rows", [])
    folder = data.get("folder", "").strip()

    if not title:
        return "Please provide a title for the spreadsheet."

    try:
        drive_svc = _get_drive_service()
        sheets_svc = _get_sheets_service()
    except Exception as e:
        logger.error(f"Sheets not available: {e}")
        return "❌ Google Sheets not available. Check OAuth credentials and run /connect google auth drive."

    try:
        # Create spreadsheet
        spreadsheet = (
            sheets_svc.spreadsheets().create(body={"properties": {"title": title}}).execute()
        )
        sheet_id = spreadsheet["spreadsheetId"]
        sheet_name = spreadsheet["sheets"][0]["properties"]["title"]  # e.g. "Sheet1"

        # Populate headers + rows
        all_rows = []
        if headers:
            all_rows.append(headers)
        all_rows.extend(rows)

        if all_rows:
            sheets_svc.spreadsheets().values().update(
                spreadsheetId=sheet_id,
                range=f"{sheet_name}!A1",
                valueInputOption="USER_ENTERED",
                body={"values": all_rows},
            ).execute()

            # Bold the header row
            if headers:
                sheets_svc.spreadsheets().batchUpdate(
                    spreadsheetId=sheet_id,
                    body={
                        "requests": [
                            {
                                "repeatCell": {
                                    "range": {
                                        "sheetId": spreadsheet["sheets"][0]["properties"][
                                            "sheetId"
                                        ],
                                        "startRowIndex": 0,
                                        "endRowIndex": 1,
                                    },
                                    "cell": {"userEnteredFormat": {"textFormat": {"bold": True}}},
                                    "fields": "userEnteredFormat.textFormat.bold",
                                }
                            }
                        ]
                    },
                ).execute()

        # Move to folder if specified
        if folder:
            folder_id = _resolve_folder_id(drive_svc, folder)
            if folder_id:
                file_meta = drive_svc.files().get(fileId=sheet_id, fields="parents").execute()
                prev = ",".join(file_meta.get("parents", []))
                drive_svc.files().update(
                    fileId=sheet_id, addParents=folder_id, removeParents=prev, fields="id, parents"
                ).execute()

        link = f"https://docs.google.com/spreadsheets/d/{sheet_id}/edit"
        location = f" in '{folder}'" if folder else ""
        row_note = f" ({len(headers)} cols, {len(rows)} rows)" if headers else ""

        return (
            f"✅ Created sheet: **{title}**{location}{row_note}\n   ID: {sheet_id}\n   Link: {link}"
        )

    except Exception as e:
        logger.error(f"Sheet creation failed: {e}")
        logger.error(f"Sheet creation failed: {e}")
        return "❌ Failed to create spreadsheet. Check Drive permissions and try again."


def move_file(data: dict) -> str:
    """
    Move a Drive file into a named folder.

    Resolves both the file and folder by name if IDs not provided.
    Useful for filing generated docs into the correct project folder.
    """
    file_id = data.get("file_id", "").strip()
    file_name = data.get("file_name", "").strip()
    folder_id = data.get("folder_id", "").strip()
    folder_name = data.get("folder_name", "").strip()

    try:
        service = _get_drive_service()
    except Exception as e:
        logger.error(f"Drive not available: {e}")
        return "❌ Google Drive not available. Check OAuth credentials and run /connect google auth drive."

    # Resolve file
    if file_name and not file_id:
        try:
            res = (
                service.files()
                .list(
                    q=f"name contains '{file_name}' and trashed=false",
                    fields="files(id, name)",
                    pageSize=1,
                )
                .execute()
            )
            files = res.get("files", [])
            if not files:
                return f"File '{file_name}' not found."
            file_id = files[0]["id"]
            file_name = files[0]["name"]
        except Exception as e:
            logger.error(f"File lookup failed: {e}")
        return "❌ Could not find the file in Drive. Check the name and try again."

    if not file_id:
        return "Please provide file_id or file_name."

    # Resolve folder
    if folder_name and not folder_id:
        folder_id = _resolve_folder_id(service, folder_name)
        if not folder_id:
            return f"Folder '{folder_name}' not found in Drive."

    if not folder_id:
        return "Please provide folder_id or folder_name."

    try:
        file_meta = service.files().get(fileId=file_id, fields="name, parents").execute()
        prev_parents = ",".join(file_meta.get("parents", []))
        display_name = file_meta.get("name", file_name or file_id)

        service.files().update(
            fileId=file_id, addParents=folder_id, removeParents=prev_parents, fields="id, parents"
        ).execute()

        return f"✅ Moved **{display_name}** → {folder_name or folder_id}"

    except Exception as e:
        logger.error(f"Move failed: {e}")
        logger.error(f"File move failed: {e}")
        return "❌ Failed to move file. Check Drive permissions and folder access."


# ══════════════════════════════════════════════════════════════════════════════
# Tool definitions
# ══════════════════════════════════════════════════════════════════════════════

TOOLS = [
    # ── Read / search ──────────────────────────────────────────────────────────
    {
        "name": "drive_search",
        "description": (
            "Search for files in Google Drive by name or keyword. "
            "Use this when the user wants to find specific documents, sheets, or folders in their Drive. "
            "Supports filtering by file type (doc, sheet, slides, pdf, folder). "
            "Returns file names, IDs, sizes, and modification dates."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Search query (file name or keywords)"},
                "type": {
                    "type": "string",
                    "description": "Filter by type: doc, sheet, slides, pdf, folder",
                },
                "limit": {
                    "type": "integer",
                    "default": 10,
                    "description": "Maximum number of results to return",
                },
            },
            "required": ["query"],
        },
        "handler": search_files,
        "category": "gdrive",
    },
    {
        "name": "drive_list",
        "description": (
            "List the contents of a Google Drive folder by name or ID. "
            "Use this to browse folder hierarchies or see what files exist in a project folder. "
            "Defaults to the root Drive folder if no folder is specified. "
            "Returns up to 30 items with names, types, and sizes."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "folder_name": {"type": "string", "description": "Folder name to list"},
                "folder_id": {
                    "type": "string",
                    "description": "Folder ID (use 'root' for top level)",
                    "default": "root",
                },
            },
        },
        "handler": list_folder,
        "category": "gdrive",
    },
    {
        "name": "drive_read",
        "description": (
            "Read the content of a Google Doc or Sheet by name or ID. "
            "Use this to view document text or spreadsheet data without downloading. "
            "Google Docs are exported as plain text; Sheets as CSV (first 50 rows). "
            "Provide either file_name (searched) or file_id (direct lookup)."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "file_name": {"type": "string", "description": "File name to search for"},
                "file_id": {"type": "string", "description": "File ID from Drive"},
            },
        },
        "handler": read_doc,
        "category": "gdrive",
    },
    {
        "name": "drive_download",
        "description": (
            "Download a file from Google Drive to the local downloads directory. "
            "Use this when the user needs a local copy of a Drive file for offline access or processing. "
            "Google Docs/Slides export as PDF; Sheets export as XLSX. "
            "Returns the local file path on success."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "file_name": {
                    "type": "string",
                    "description": "File name to search for in Drive",
                },
                "file_id": {
                    "type": "string",
                    "description": "File ID for direct lookup (preferred over name)",
                },
            },
        },
        "handler": download_file,
        "category": "gdrive",
    },
    {
        "name": "drive_recent",
        "description": (
            "Get the most recently modified files in Google Drive. "
            "Use this to see what the user or collaborators have been working on recently. "
            "Results are sorted by modification time, newest first. "
            "Returns file names, types, and modification timestamps."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "limit": {
                    "type": "integer",
                    "default": 10,
                    "description": "Maximum number of recent files to return",
                },
            },
        },
        "handler": recent_files,
        "category": "gdrive",
    },
    # ── Write / create ─────────────────────────────────────────────────────────
    {
        "name": "drive_create_doc",
        "description": (
            "Create a new Google Doc with a title and content. "
            "Supports markdown headings (# H1, ## H2, ### H3). "
            "Optionally places the doc in a named Drive folder. "
            "Use for grant reports, meeting minutes, donor letters — any "
            "document the team should be able to open and edit in Drive."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "title": {"type": "string", "description": "Document title"},
                "content": {
                    "type": "string",
                    "description": "Document body (supports # ## ### headings)",
                },
                "folder": {
                    "type": "string",
                    "description": "Drive folder name to place the doc in (optional)",
                },
            },
            "required": ["title"],
        },
        "handler": create_doc,
        "category": "gdrive",
    },
    {
        "name": "drive_append_doc",
        "description": (
            "Append text to an existing Google Doc (by name or ID). "
            "Adds content at the end, with a dated separator line by default. "
            "Ideal for accumulating meeting minutes or adding sections to grant reports. "
            "Only works on docs created by Familiar, or docs shared with edit access."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "file_name": {"type": "string", "description": "Document name to search for"},
                "file_id": {"type": "string", "description": "Document ID"},
                "content": {"type": "string", "description": "Text to append"},
                "append_separator": {
                    "type": "boolean",
                    "description": "Add a dated separator line before content",
                    "default": True,
                },
            },
            "required": ["content"],
        },
        "handler": append_doc,
        "category": "gdrive",
    },
    {
        "name": "drive_update_doc",
        "description": (
            "Replace the full content of an existing Google Doc (by name or ID). "
            "Clears the existing body and writes new content. "
            "Use drive_append_doc to add without erasing. "
            "Only works on docs created by Familiar, or docs shared with edit access."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "file_name": {"type": "string", "description": "Document name to search for"},
                "file_id": {"type": "string", "description": "Document ID"},
                "content": {
                    "type": "string",
                    "description": "New full content (supports # ## ### headings)",
                },
            },
            "required": ["content"],
        },
        "handler": update_doc,
        "category": "gdrive",
    },
    {
        "name": "drive_create_sheet",
        "description": (
            "Create a new Google Sheet with headers and optional data rows. "
            "Useful for donor lists, action item trackers, budget tables. "
            "Headers are bolded automatically."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "title": {"type": "string", "description": "Spreadsheet title"},
                "headers": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Column header names",
                },
                "rows": {
                    "type": "array",
                    "items": {"type": "array"},
                    "description": "Data rows (list of lists)",
                },
                "folder": {"type": "string", "description": "Drive folder name (optional)"},
            },
            "required": ["title"],
        },
        "handler": create_sheet,
        "category": "gdrive",
    },
    {
        "name": "drive_move",
        "description": (
            "Move a Drive file into a named folder. "
            "Useful for filing generated documents into the correct project folder "
            "after creation. Resolves file and folder by name if IDs not provided."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "file_name": {"type": "string", "description": "File name to move"},
                "file_id": {"type": "string", "description": "File ID to move"},
                "folder_name": {"type": "string", "description": "Destination folder name"},
                "folder_id": {"type": "string", "description": "Destination folder ID"},
            },
        },
        "handler": move_file,
        "category": "gdrive",
    },
]
