"""download_release_docs -- Download documents from Google Drive.

Reads the .md link files created by get_release_info and downloads
the actual PDFs from Google Drive. Downloads are parallel (up to 4
concurrent) and idempotent -- existing files are skipped.

The AI should call this tool per-package or in small batches to stay
within timeout limits. If a call is interrupted, re-running with the
same parameters resumes from where it left off (skips already-
downloaded files).

Some Google Drive links point to zip archives containing multiple
PDFs. These are extracted automatically and the zip is removed.
"""

import asyncio
import logging
import re
import shutil
import ssl
import tempfile
import zipfile
from pathlib import Path
from typing import Any
from urllib.error import HTTPError, URLError
from urllib.request import urlopen, Request

from ..utils import state

logger = logging.getLogger(__name__)

MAX_CONCURRENT = 4
DOWNLOAD_TIMEOUT = 30

def _build_ssl_context() -> ssl.SSLContext:
    """Build an SSL context that works on macOS.

    Tries certifi first (if installed), then the default system store,
    then falls back to unverified for Google Drive (trusted domain).
    """
    try:
        import certifi  # noqa: F811
        ctx = ssl.create_default_context(cafile=certifi.where())
        return ctx
    except ImportError:
        pass

    ctx = ssl.create_default_context()
    try:
        ctx.load_default_certs()
    except Exception:
        pass

    try:
        urlopen(
            Request("https://drive.google.com", method="HEAD"),
            timeout=5, context=ctx,
        )
        return ctx
    except Exception:
        logger.warning(
            "SSL verification failed for Google Drive. "
            "Using unverified context. Install certifi or run "
            "'Install Certificates.command' from your Python installation "
            "to fix this permanently."
        )
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
        return ctx

_SSL_CTX: ssl.SSLContext | None = None

def _get_ssl_context() -> ssl.SSLContext:
    global _SSL_CTX
    if _SSL_CTX is None:
        _SSL_CTX = _build_ssl_context()
    return _SSL_CTX

GDRIVE_EXPORT_TPL = "https://drive.google.com/uc?export=download&id={file_id}"
GDRIVE_ID_PATTERN = re.compile(r"[?&]id=([a-zA-Z0-9_-]+)")
LINK_PATTERN = re.compile(r"\*\*Link:\*\*\s*\[([^\]]*)\]\(([^)]+)\)")

TOOL_DEFINITION = {
    "name": "download_release_docs",
    "description": (
        "Download release notes and user guide PDFs from Google Drive. "
        "Reads the .md link files written by get_release_info and "
        "downloads the actual documents. Downloads are parallel and "
        "idempotent (existing files are skipped). Call per-package or "
        "in small batches to stay within timeout."
    ),
    "inputSchema": {
        "type": "object",
        "properties": {
            "working_directory": {
                "type": "string",
                "description": "Absolute path to the workspace root directory.",
            },
            "account_name": {
                "type": "string",
                "description": (
                    "Optional customer account name. Determines the base "
                    "directory: customers/<slug>/release-docs/ if set, "
                    "otherwise release-docs/."
                ),
            },
            "packages": {
                "type": "array",
                "items": {"type": "string"},
                "description": (
                    "Optional list of package folder names to download. "
                    "Use the folder names from release-docs/ (e.g. "
                    "'CloudSense_CPQ_(Configurator)'). If omitted, "
                    "downloads all packages in the release-docs folder."
                ),
            },
        },
        "required": ["working_directory"],
    },
}


def _make_slug(name: str) -> str:
    slug = name.lower().strip()
    slug = re.sub(r"[^a-z0-9]+", "-", slug)
    slug = slug.strip("-")
    return slug or "unknown"


def _extract_gdrive_id(url: str) -> str | None:
    """Extract the Google Drive file ID from a URL."""
    m = GDRIVE_ID_PATTERN.search(url)
    return m.group(1) if m else None


def _scan_md_files(
    base_dir: Path, package_filter: list[str] | None,
) -> list[dict[str, Any]]:
    """Scan release-docs/ for .md files and extract download targets.

    Returns a list of {md_path, doc_name, url, file_id, target_dir}.
    """
    targets: list[dict[str, Any]] = []

    if not base_dir.exists():
        return targets

    filter_lower = {p.lower() for p in package_filter} if package_filter else None

    for pkg_dir in sorted(base_dir.iterdir()):
        if not pkg_dir.is_dir() or pkg_dir.name == "__pycache__":
            continue

        if filter_lower and pkg_dir.name.lower() not in filter_lower:
            continue

        for release_dir in sorted(pkg_dir.iterdir()):
            if not release_dir.is_dir():
                continue

            for md_file in sorted(release_dir.glob("*.md")):
                content = md_file.read_text(errors="replace")
                for match in LINK_PATTERN.finditer(content):
                    doc_name = match.group(1)
                    url = match.group(2)
                    file_id = _extract_gdrive_id(url)
                    if file_id:
                        targets.append({
                            "md_path": md_file,
                            "doc_name": doc_name,
                            "url": url,
                            "file_id": file_id,
                            "target_dir": release_dir,
                            "package": pkg_dir.name,
                            "release": release_dir.name,
                        })

    return targets


def _is_already_downloaded(target: dict[str, Any]) -> bool:
    """Check if a target has already been downloaded."""
    target_dir = target["target_dir"]
    file_id = target["file_id"]
    for f in target_dir.iterdir():
        if f.is_file() and file_id in f.name and f.suffix != ".md":
            return True
    return False


def _download_one(target: dict[str, Any]) -> dict[str, Any]:
    """Download a single file from Google Drive. Returns result dict."""
    file_id = target["file_id"]
    doc_name = target["doc_name"]
    target_dir = target["target_dir"]

    download_url = GDRIVE_EXPORT_TPL.format(file_id=file_id)

    try:
        req = Request(download_url, headers={"User-Agent": "CloudSense-Compass/1.0"})
        ctx = _get_ssl_context()
        with urlopen(req, timeout=DOWNLOAD_TIMEOUT, context=ctx) as resp:
            content_type = resp.headers.get("Content-Type", "")
            data = resp.read()
    except HTTPError as e:
        return {
            **target, "status": "failed",
            "error": f"HTTP {e.code}: {e.reason}",
        }
    except URLError as e:
        return {
            **target, "status": "failed",
            "error": f"Network error: {e.reason}",
        }
    except TimeoutError:
        return {
            **target, "status": "failed",
            "error": "Download timed out",
        }

    safe_name = re.sub(r"[^\w\s\-.]", "", doc_name).strip()
    safe_name = re.sub(r"\s+", "_", safe_name)
    if not safe_name:
        safe_name = f"doc_{file_id[:8]}"

    with tempfile.NamedTemporaryFile(delete=False, suffix=".tmp") as tmp:
        tmp.write(data)
        tmp_path = Path(tmp.name)

    try:
        if zipfile.is_zipfile(tmp_path):
            return _handle_zip(tmp_path, target_dir, file_id, target)
        else:
            ext = ".pdf"
            if "spreadsheet" in content_type:
                ext = ".xlsx"
            elif "presentation" in content_type:
                ext = ".pptx"
            elif "wordprocessing" in content_type or "msword" in content_type:
                ext = ".docx"

            final_name = f"{safe_name}__{file_id}{ext}"
            final_path = target_dir / final_name
            shutil.move(str(tmp_path), str(final_path))

            return {
                **target, "status": "downloaded",
                "path": str(final_path),
                "size": len(data),
            }
    except Exception as e:
        return {
            **target, "status": "failed",
            "error": f"File handling error: {e}",
        }
    finally:
        if tmp_path.exists():
            tmp_path.unlink(missing_ok=True)


def _handle_zip(
    zip_path: Path, target_dir: Path, file_id: str, target: dict[str, Any],
) -> dict[str, Any]:
    """Extract a zip archive and return result."""
    extracted_files: list[str] = []
    try:
        with zipfile.ZipFile(zip_path) as zf:
            for member in zf.namelist():
                if member.startswith("__MACOSX") or member.startswith("."):
                    continue
                member_name = Path(member).name
                if not member_name:
                    continue
                member_path = Path(member_name)
                stem = re.sub(r"[^\w\s\-]", "", member_path.stem).strip()
                stem = re.sub(r"\s+", "_", stem)
                if not stem:
                    continue
                ext = member_path.suffix or ".pdf"
                out_name = f"{stem}__{file_id}{ext}"
                out_path = target_dir / out_name
                with zf.open(member) as src, open(out_path, "wb") as dst:
                    dst.write(src.read())
                extracted_files.append(str(out_path))
    except zipfile.BadZipFile:
        return {
            **target, "status": "failed",
            "error": "Downloaded file is a corrupt zip",
        }
    finally:
        zip_path.unlink(missing_ok=True)

    return {
        **target, "status": "downloaded",
        "path": ", ".join(extracted_files),
        "size": sum(Path(f).stat().st_size for f in extracted_files if Path(f).exists()),
        "extracted": len(extracted_files),
    }


def _update_md_status(
    results: list[dict[str, Any]],
    skipped: list[dict[str, Any]],
) -> None:
    """Update .md files with download status markers.

    Adds a **Status:** line after each **Link:** line so users can see
    at a glance which documents have been downloaded.
    """
    status_by_url: dict[str, dict[str, str]] = {}

    for r in results:
        url = r.get("url", "")
        if not url:
            continue
        if r["status"] == "downloaded":
            paths = r.get("path", "")
            if paths:
                filenames = ", ".join(Path(p.strip()).name for p in paths.split(","))
            else:
                filenames = "unknown"
            status_by_url[url] = {
                "marker": f"**Status:** Downloaded → {filenames}",
            }
        elif r["status"] == "failed":
            status_by_url[url] = {
                "marker": f"**Status:** Failed — {r.get('error', 'unknown')}",
            }

    for s in skipped:
        url = s.get("url", "")
        if url and url not in status_by_url:
            file_id = s.get("file_id", "")
            target_dir = s.get("target_dir")
            if target_dir:
                local = [
                    f.name for f in target_dir.iterdir()
                    if f.is_file() and file_id in f.name and f.suffix != ".md"
                ]
                if local:
                    status_by_url[url] = {
                        "marker": f"**Status:** Downloaded → {', '.join(local)}",
                    }

    md_files: dict[Path, list[dict[str, Any]]] = {}
    for r in results + skipped:
        md_path = r.get("md_path")
        if md_path:
            md_files.setdefault(md_path, []).append(r)

    status_pattern = re.compile(r"\*\*Status:\*\*.*\n?")

    for md_path in md_files:
        if not md_path.exists():
            continue
        content = md_path.read_text(errors="replace")

        content = status_pattern.sub("", content)

        for url, info in status_by_url.items():
            link_escaped = re.escape(url)
            pattern = re.compile(
                rf"(\*\*Link:\*\*\s*\[[^\]]*\]\({link_escaped}\)\s*\n)"
            )
            replacement = rf"\1{info['marker']}\n"
            content = pattern.sub(replacement, content)

        md_path.write_text(content)


async def run(arguments: dict[str, Any]) -> str:
    """Execute the download_release_docs tool."""
    state.set_working_dir(arguments.get("working_directory"))
    working_dir = state.get_working_dir()

    account_name = arguments.get("account_name", "").strip() or None
    package_filter = arguments.get("packages") or None

    if account_name:
        base_dir = working_dir / "customers" / _make_slug(account_name) / "release-docs"
    else:
        base_dir = working_dir / "release-docs"

    if not base_dir.exists():
        return (
            f"STATUS: NO_RELEASE_DOCS\n\n"
            f"No release-docs folder found at {base_dir}.\n\n"
            f"Call get_release_info first to create the folder structure "
            f"with document links, then call this tool to download them."
        )

    targets = _scan_md_files(base_dir, package_filter)

    if not targets:
        hint = ""
        if package_filter:
            available = sorted(
                d.name for d in base_dir.iterdir() if d.is_dir() and d.name != "__pycache__"
            )
            hint = f"\n\nAvailable packages: {', '.join(available)}" if available else ""
        return (
            f"STATUS: NO_TARGETS\n\n"
            f"No downloadable document links found in {base_dir}."
            + (f"\nPackage filter: {', '.join(package_filter)}" if package_filter else "")
            + hint
            + "\n\nMake sure get_release_info has been called first."
        )

    to_download: list[dict[str, Any]] = []
    skipped: list[dict[str, Any]] = []

    for t in targets:
        if _is_already_downloaded(t):
            skipped.append(t)
        else:
            to_download.append(t)

    if not to_download:
        pkg_set = sorted(set(t["package"] for t in skipped))
        return (
            f"STATUS: ALL_DOWNLOADED\n\n"
            f"All {len(skipped)} documents have already been downloaded.\n"
            f"Packages: {', '.join(pkg_set)}\n\n"
            f"No action needed. If you want to re-download, delete the "
            f"existing files first."
        )

    results: list[dict[str, Any]] = []
    loop = asyncio.get_running_loop()

    semaphore = asyncio.Semaphore(MAX_CONCURRENT)

    async def _download_with_limit(target: dict[str, Any]) -> dict[str, Any]:
        async with semaphore:
            return await loop.run_in_executor(None, _download_one, target)

    tasks = [_download_with_limit(t) for t in to_download]
    results = await asyncio.gather(*tasks)

    downloaded = [r for r in results if r["status"] == "downloaded"]
    failed = [r for r in results if r["status"] == "failed"]
    total_size = sum(r.get("size", 0) for r in downloaded)

    _update_md_status(results, skipped)

    pkg_stats: dict[str, dict[str, int]] = {}
    for r in downloaded:
        pkg = r.get("package", "?")
        pkg_stats.setdefault(pkg, {"downloaded": 0, "skipped": 0, "failed": 0})
        pkg_stats[pkg]["downloaded"] += 1
    for s in skipped:
        pkg = s.get("package", "?")
        pkg_stats.setdefault(pkg, {"downloaded": 0, "skipped": 0, "failed": 0})
        pkg_stats[pkg]["skipped"] += 1
    for f in failed:
        pkg = f.get("package", "?")
        pkg_stats.setdefault(pkg, {"downloaded": 0, "skipped": 0, "failed": 0})
        pkg_stats[pkg]["failed"] += 1

    size_mb = total_size / (1024 * 1024)

    lines = [
        "STATUS: READY\n",
        "## Download Results\n",
        f"Downloaded: {len(downloaded)} files ({size_mb:.1f} MB)",
        f"Skipped (already exist): {len(skipped)}",
        f"Failed: {len(failed)}",
        f"Total: {len(targets)}",
        "",
        "### Per-package:\n",
    ]

    for pkg in sorted(pkg_stats.keys()):
        stats = pkg_stats[pkg]
        parts = []
        if stats["downloaded"]:
            parts.append(f"{stats['downloaded']} downloaded")
        if stats["skipped"]:
            parts.append(f"{stats['skipped']} skipped")
        if stats["failed"]:
            parts.append(f"{stats['failed']} FAILED")
        lines.append(f"  {pkg}: {', '.join(parts)}")

    if failed:
        lines.append("\n### Failed downloads:\n")
        for f in failed:
            lines.append(
                f"  - {f.get('package','?')}/{f.get('release','?')}: "
                f"{f.get('doc_name','?')} -- {f.get('error','unknown')}"
            )
        lines.append(
            "\nTo retry failed downloads, call this tool again with the "
            "same parameters. Already-downloaded files will be skipped."
        )

    lines.extend([
        "",
        "---",
        f"Output directory: {base_dir}",
        "",
        "Present the download summary to the user.",
        "If there are failures, offer to retry.",
        "If all succeeded, the user can now browse the documents locally.",
    ])

    return "\n".join(lines)
