import shutil as _shutil
from pathlib import Path

import requests

from backend.config import (
    GOOGLE_API_SUPPORT,
    MICROSOFT_GRAPH_URL,
    OUTPUT_DIR,
    _google_service,
    _load_settings,
    _microsoft_graph_headers,
)


_GDRIVE_EXPORT_MAP = {
    "application/vnd.google-apps.document": ("text/plain", ".txt"),
    "application/vnd.google-apps.spreadsheet": ("text/csv", ".csv"),
    "application/vnd.google-apps.presentation": ("text/plain", ".txt"),
}


def _google_drive_list(folder_id: str = "root", count: int = 30) -> str:
    if not GOOGLE_API_SUPPORT:
        return "Google API libraries not installed."
    service = _google_service("drive", "v3")
    if not service:
        return "Google not authorized. Ask the user to click 'Authorize Google' in Settings → Integrations."
    try:
        query = f"'{folder_id}' in parents and trashed = false"
        resp = service.files().list(
            q=query, pageSize=min(count, 100),
            fields="files(id, name, mimeType, size, modifiedTime)",
            orderBy="folder,name",
        ).execute()
        files = resp.get("files", [])
        if not files:
            return "No files found in this folder."
        lines = []
        for file_item in files:
            is_folder = file_item.get("mimeType") == "application/vnd.google-apps.folder"
            size = file_item.get("size", "-")
            icon = "[Folder]" if is_folder else "[File]"
            lines.append(
                f"{icon} {file_item['name']}\n"
                f"  ID: {file_item['id']}\n"
                f"  Type: {file_item.get('mimeType', '?')}\n"
                f"  Size: {size}\n"
                f"  Modified: {file_item.get('modifiedTime', '?')}"
            )
        header = f"Google Drive — {len(files)} item(s):\n\n"
        return header + "\n\n".join(lines)
    except Exception as e:
        return f"Google Drive list error: {e}"


def _google_drive_search(query: str, count: int = 20) -> str:
    if not GOOGLE_API_SUPPORT:
        return "Google API libraries not installed."
    service = _google_service("drive", "v3")
    if not service:
        return "Google not authorized. Ask the user to click 'Authorize Google' in Settings → Integrations."
    try:
        search_query = f"name contains '{query.replace(chr(39), '')}' and trashed = false"
        resp = service.files().list(
            q=search_query, pageSize=min(count, 100),
            fields="files(id, name, mimeType, size, modifiedTime)",
        ).execute()
        files = resp.get("files", [])
        if not files:
            return f"No files matching '{query}' found."
        lines = []
        for file_item in files:
            lines.append(
                f"{file_item['name']}\n"
                f"  ID: {file_item['id']}\n"
                f"  Type: {file_item.get('mimeType', '?')}\n"
                f"  Size: {file_item.get('size', '-')}"
            )
        return f"Search results for '{query}' ({len(files)}):\n\n" + "\n\n".join(lines)
    except Exception as e:
        return f"Google Drive search error: {e}"


def _google_drive_read(file_id: str) -> str:
    if not GOOGLE_API_SUPPORT:
        return "Google API libraries not installed."
    service = _google_service("drive", "v3")
    if not service:
        return "Google not authorized. Ask the user to click 'Authorize Google' in Settings → Integrations."
    try:
        meta = service.files().get(fileId=file_id, fields="name,mimeType,size").execute()
        mime = meta.get("mimeType", "")
        name = meta.get("name", "unknown")
        export = _GDRIVE_EXPORT_MAP.get(mime)
        if export:
            export_mime, _ = export
            content = service.files().export(fileId=file_id, mimeType=export_mime).execute()
            if isinstance(content, bytes):
                content = content.decode("utf-8", errors="replace")
            return f"=== {name} (exported as {export_mime}) ===\n\n{content[:50000]}"
        content = service.files().get_media(fileId=file_id).execute()
        if isinstance(content, bytes):
            try:
                text = content.decode("utf-8")
                return f"=== {name} ===\n\n{text[:50000]}"
            except UnicodeDecodeError:
                dest = OUTPUT_DIR / name
                dest.write_bytes(content)
                return f"Binary file '{name}' downloaded to output/{name} ({len(content):,} bytes)."
        return f"=== {name} ===\n\n{str(content)[:50000]}"
    except Exception as e:
        return f"Google Drive read error: {e}"


def _google_drive_upload(name: str, content: str, folder_id: str = "root", mime_type: str = "text/plain") -> str:
    if not GOOGLE_API_SUPPORT:
        return "Google API libraries not installed."
    service = _google_service("drive", "v3")
    if not service:
        return "Google not authorized. Ask the user to click 'Authorize Google' in Settings → Integrations."
    try:
        from googleapiclient.http import MediaInMemoryUpload

        file_meta = {"name": name, "parents": [folder_id]}
        media = MediaInMemoryUpload(content.encode("utf-8"), mimetype=mime_type, resumable=False)
        created = service.files().create(
            body=file_meta, media_body=media, fields="id,name,webViewLink",
        ).execute()
        link = created.get("webViewLink", "")
        return f"File '{created['name']}' uploaded to Google Drive (ID: {created['id']}).{' Link: ' + link if link else ''}"
    except Exception as e:
        return f"Google Drive upload error: {e}"


def _is_onedrive_local() -> tuple:
    """Check if local OneDrive mode is active. Returns (True, base_path) or (False, None)."""
    settings = _load_settings()
    if settings.get("onedrive_mode", "local") == "local":
        path_value = (settings.get("onedrive_local_path") or "").strip()
        if path_value and Path(path_value).is_dir():
            return True, Path(path_value)
    return False, None


def _onedrive_list(folder_path: str = "", count: int = 30) -> str:
    local, base = _is_onedrive_local()
    if local:
        try:
            target = (base / folder_path).resolve() if folder_path and folder_path != "/" else base.resolve()
            if not target.is_relative_to(base.resolve()):
                return "Path outside OneDrive folder."
            if not target.is_dir():
                return f"Folder not found: {folder_path}"
            entries = sorted(target.iterdir(), key=lambda entry: (not entry.is_dir(), entry.name.lower()))[:count]
            if not entries:
                return "No files found in this folder."
            lines = []
            for entry in entries:
                icon = "[Folder]" if entry.is_dir() else "[File]"
                size = "-" if entry.is_dir() else entry.stat().st_size
                rel = str(entry.relative_to(base.resolve())).replace("\\", "/")
                lines.append(f"{icon} {entry.name}\n  Path: {rel}\n  Size: {size}")
            return f"OneDrive (local) - {len(entries)} item(s):\n\n" + "\n\n".join(lines)
        except Exception as e:
            return f"OneDrive local list error: {e}"
    headers = _microsoft_graph_headers()
    if not headers:
        return "OneDrive not authorized. Ask the user to click 'Authorize Microsoft' in Settings → Integrations."
    try:
        if folder_path and folder_path != "/":
            clean = folder_path.strip("/")
            url = f"{MICROSOFT_GRAPH_URL}/me/drive/root:/{clean}:/children"
        else:
            url = f"{MICROSOFT_GRAPH_URL}/me/drive/root/children"
        resp = requests.get(url, headers=headers, params={"$top": min(count, 200)}, timeout=15)
        resp.raise_for_status()
        items = resp.json().get("value", [])
        if not items:
            return "No files found in this folder."
        lines = []
        for item in items:
            is_folder = "folder" in item
            icon = "[Folder]" if is_folder else "[File]"
            size = item.get("size", "-")
            lines.append(
                f"{icon} {item['name']}\n"
                f"  ID: {item['id']}\n"
                f"  Size: {size}\n"
                f"  Modified: {item.get('lastModifiedDateTime', '?')}"
            )
        header = f"OneDrive — {len(items)} item(s):\n\n"
        return header + "\n\n".join(lines)
    except Exception as e:
        return f"OneDrive list error: {e}"


def _onedrive_search(query: str, count: int = 20) -> str:
    local, base = _is_onedrive_local()
    if local:
        try:
            query_lower = query.lower()
            matches = []
            for path in base.resolve().rglob("*"):
                if query_lower in path.name.lower():
                    rel = str(path.relative_to(base.resolve())).replace("\\", "/")
                    icon = "[Folder]" if path.is_dir() else "[File]"
                    size = "-" if path.is_dir() else path.stat().st_size
                    matches.append(f"{icon} {path.name}\n  Path: {rel}\n  Size: {size}")
                    if len(matches) >= count:
                        break
            if not matches:
                return f"No files matching '{query}' found in local OneDrive."
            return f"Search results for '{query}' ({len(matches)}):\n\n" + "\n\n".join(matches)
        except Exception as e:
            return f"OneDrive local search error: {e}"
    headers = _microsoft_graph_headers()
    if not headers:
        return "OneDrive not authorized. Ask the user to click 'Authorize Microsoft' in Settings → Integrations."
    try:
        url = f"{MICROSOFT_GRAPH_URL}/me/drive/root/search(q='{query}')"
        resp = requests.get(url, headers=headers, params={"$top": min(count, 100)}, timeout=15)
        resp.raise_for_status()
        items = resp.json().get("value", [])
        if not items:
            return f"No files matching '{query}' found."
        lines = []
        for item in items:
            parent_path = item.get("parentReference", {}).get("path", "")
            lines.append(
                f"{item['name']}\n"
                f"  ID: {item['id']}\n"
                f"  Size: {item.get('size', '-')}\n"
                f"  Path: {parent_path}"
            )
        return f"Search results for '{query}' ({len(items)}):\n\n" + "\n\n".join(lines)
    except Exception as e:
        return f"OneDrive search error: {e}"


def _onedrive_read(file_id: str) -> str:
    local, base = _is_onedrive_local()
    if local:
        try:
            target = (base / file_id).resolve()
            if not str(target).startswith(str(base.resolve())):
                return "Path outside OneDrive folder."
            if not target.is_file():
                return f"File not found: {file_id}"
            try:
                text = target.read_text(encoding="utf-8")
                return f"=== {target.name} ===\n\n{text[:50000]}"
            except UnicodeDecodeError:
                dest = OUTPUT_DIR / target.name
                _shutil.copy2(target, dest)
                return f"Binary file '{target.name}' copied to output/{target.name} ({target.stat().st_size:,} bytes)."
        except Exception as e:
            return f"OneDrive local read error: {e}"
    headers = _microsoft_graph_headers()
    if not headers:
        return "OneDrive not authorized. Ask the user to click 'Authorize Microsoft' in Settings → Integrations."
    try:
        meta_resp = requests.get(
            f"{MICROSOFT_GRAPH_URL}/me/drive/items/{file_id}",
            headers=headers, timeout=15,
        )
        meta_resp.raise_for_status()
        meta = meta_resp.json()
        name = meta.get("name", "unknown")
        download_url = meta.get("@microsoft.graph.downloadUrl", "")
        if not download_url:
            download_url = f"{MICROSOFT_GRAPH_URL}/me/drive/items/{file_id}/content"
        content_resp = requests.get(download_url, timeout=30)
        content_resp.raise_for_status()
        try:
            text = content_resp.content.decode("utf-8")
            return f"=== {name} ===\n\n{text[:50000]}"
        except UnicodeDecodeError:
            dest = OUTPUT_DIR / name
            dest.write_bytes(content_resp.content)
            return f"Binary file '{name}' downloaded to output/{name} ({len(content_resp.content):,} bytes)."
    except Exception as e:
        return f"OneDrive read error: {e}"


def _onedrive_upload(name: str, content: str, folder_path: str = "") -> str:
    local, base = _is_onedrive_local()
    if local:
        try:
            if folder_path and folder_path != "/":
                target_dir = (base / folder_path.strip("/")).resolve()
            else:
                target_dir = base.resolve()
            if not str(target_dir).startswith(str(base.resolve())):
                return "Path outside OneDrive folder."
            target_dir.mkdir(parents=True, exist_ok=True)
            dest = target_dir / name
            dest.write_text(content, encoding="utf-8")
            rel = str(dest.relative_to(base.resolve())).replace("\\", "/")
            return f"File '{name}' saved to OneDrive (local) at {rel}. It will sync automatically."
        except Exception as e:
            return f"OneDrive local upload error: {e}"
    headers = _microsoft_graph_headers()
    if not headers:
        return "OneDrive not authorized. Ask the user to click 'Authorize Microsoft' in Settings → Integrations."
    try:
        if folder_path and folder_path != "/":
            clean = folder_path.strip("/")
            url = f"{MICROSOFT_GRAPH_URL}/me/drive/root:/{clean}/{name}:/content"
        else:
            url = f"{MICROSOFT_GRAPH_URL}/me/drive/root:/{name}:/content"
        resp = requests.put(
            url,
            headers={"Authorization": headers["Authorization"], "Content-Type": "application/octet-stream"},
            data=content.encode("utf-8"),
            timeout=30,
        )
        resp.raise_for_status()
        item = resp.json()
        link = item.get("webUrl", "")
        return f"File '{item.get('name', name)}' uploaded to OneDrive (ID: {item.get('id', '?')}).{' Link: ' + link if link else ''}"
    except Exception as e:
        return f"OneDrive upload error: {e}"