"""GitLab Pages source connector — clones *.pages.redhat.com repos and indexes content.

Each configured site (e.g. "ansible") maps to a GitLab project at
gitlab.cee.redhat.com/{site}/{site}.pages.redhat.com. The connector
clones these repos using the GitLab PAT and indexes their content
(markdown, HTML, RST, etc.) into the oracle_pages vector collection.
"""

from __future__ import annotations

import asyncio
import logging
import ssl
import subprocess
from pathlib import Path
from typing import TYPE_CHECKING, Any, cast

import httpx
from rich.progress import Progress, TaskID

from redhat_oracle.core.config import OracleConfig, get_data_dir
from redhat_oracle.core.vectordb.chunker import chunk_repo
from redhat_oracle.core.vectordb.tracker import IndexTracker

if TYPE_CHECKING:
    from redhat_oracle.core.vectordb.store import VectorStore

logger = logging.getLogger(__name__)

# File extensions worth indexing from pages repos (docs-heavy)
_PAGES_EXTENSIONS = {
    ".md", ".rst", ".txt", ".html", ".htm", ".adoc", ".asciidoc",
    ".yaml", ".yml", ".json", ".toml",
    ".py", ".js", ".ts", ".jsx", ".tsx", ".go", ".java", ".rb",
    ".sh", ".bash", ".css", ".xml", ".svg",
}


def _get_ssl_context() -> ssl.SSLContext | bool:
    """Get SSL context using system CA bundle."""
    for ca_path in (
        "/etc/pki/tls/certs/ca-bundle.crt",
        "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
    ):
        if Path(ca_path).exists():
            return ssl.create_default_context(cafile=ca_path)
    return True


async def _find_project_id(
    client: httpx.AsyncClient,
    gitlab_url: str,
    site: str,
    token: str,
) -> dict[str, Any] | None:
    """Find the GitLab project for a pages site.

    Tries {site}/{site}.pages.redhat.com first, then searches.
    """
    headers = {"PRIVATE-TOKEN": token}

    # Try the canonical path: {site}/{site}.pages.redhat.com
    from urllib.parse import quote_plus

    project_path = f"{site}/{site}.pages.redhat.com"
    resp = await client.get(
        f"{gitlab_url}/api/v4/projects/{quote_plus(project_path)}",
        headers=headers,
    )
    if resp.status_code == 200:
        return cast(dict[str, Any], resp.json())

    # Fallback: search for the project
    resp = await client.get(
        f"{gitlab_url}/api/v4/projects",
        headers=headers,
        params={"search": f"{site}.pages.redhat.com", "per_page": 10},
    )
    if resp.status_code == 200:
        for p in resp.json():
            if p["path"] == f"{site}.pages.redhat.com" and not p.get("forked_from_project"):
                return cast(dict[str, Any], p)

    logger.warning("Could not find GitLab project for %s.pages.redhat.com", site)
    return None


def _clone_or_pull(
    clone_url: str,
    dest: Path,
    depth: int | None,
    token: str,
) -> bool:
    """Clone or pull a GitLab repo using HTTPS with PAT auth."""
    # Inject token into HTTPS URL for auth
    auth_url = clone_url.replace("https://", f"https://oauth2:{token}@")

    if dest.exists() and (dest / ".git" / "HEAD").exists():
        # Valid existing clone — fetch updates
        result = subprocess.run(
            ["git", "fetch", "--depth=1", "origin"],
            cwd=str(dest),
            capture_output=True,
            timeout=600,
        )
        return result.returncode == 0

    # Remove any partial/corrupt clone directory
    if dest.exists():
        import shutil
        shutil.rmtree(dest, ignore_errors=True)

    # Fresh clone
    dest.parent.mkdir(parents=True, exist_ok=True)
    cmd = ["git", "clone"]
    if depth is not None:
        cmd.extend(["--depth", str(depth)])
    cmd.extend([auth_url, str(dest)])

    result = subprocess.run(cmd, capture_output=True, timeout=600)
    return result.returncode == 0


async def sync_pages(
    config: OracleConfig,
    store: VectorStore | None = None,
    progress: Progress | None = None,
) -> dict[str, Any]:
    """Clone all configured GitLab Pages repos and index their content.

    When store is provided, upserts chunks directly after each site
    for incremental persistence.

    Returns:
        Dict with stats: cloned, skipped, failed, chunks.
    """
    pages_config = config.sources.pages
    token = pages_config.token or config.sources.gitlab.token

    if not token:
        logger.warning("No GitLab token configured, skipping pages sync")
        return {"cloned": 0, "failed": 0, "chunks": 0}

    if not pages_config.sites:
        logger.info("No pages sites configured, skipping")
        return {"cloned": 0, "failed": 0, "chunks": 0}

    gitlab_url = pages_config.gitlab_url
    data_dir = get_data_dir(config) / "repos" / "pages"
    verify = _get_ssl_context()
    tracker = IndexTracker(get_data_dir(config))

    stats = {"cloned": 0, "skipped": 0, "failed": 0, "chunks": 0}

    task_id: TaskID | None = None
    if progress:
        task_id = progress.add_task(
            "[cyan]Pages sites", total=len(pages_config.sites)
        )

    async with httpx.AsyncClient(verify=verify, timeout=30) as client:
        for site in pages_config.sites:
            if progress and task_id is not None:
                progress.update(
                    task_id,
                    description=f"[cyan]{site}.pages.redhat.com",
                )

            try:
                # Find the GitLab project
                project = await _find_project_id(client, gitlab_url, site, token)
                if not project:
                    stats["failed"] += 1
                    if progress and task_id is not None:
                        progress.advance(task_id)
                    continue

                # Get clone URL
                clone_url = project.get("http_url_to_repo", "")
                if not clone_url:
                    logger.warning("No clone URL for %s", site)
                    stats["failed"] += 1
                    if progress and task_id is not None:
                        progress.advance(task_id)
                    continue

                # Clone/pull the repo
                dest = data_dir / site / f"{site}.pages.redhat.com"
                repo_name = f"{site}.pages.redhat.com"
                ok = await asyncio.to_thread(
                    _clone_or_pull, clone_url, dest, pages_config.depth, token
                )

                if ok:
                    stats["cloned"] += 1

                    # Check if HEAD changed since last index
                    if not tracker.needs_reindex("pages", site, repo_name, dest):
                        stats["skipped"] += 1
                        logger.info("Skipping %s (unchanged)", repo_name)
                        if progress and task_id is not None:
                            progress.advance(task_id)
                        continue

                    # Chunk the repo content and upsert directly
                    chunks = chunk_repo(
                        dest,
                        "pages",
                        site,
                        repo_name,
                        index_extensions=_PAGES_EXTENSIONS,
                    )
                    if store and chunks:
                        store.upsert_chunks(chunks)
                    stats["chunks"] += len(chunks)

                    tracker.mark_indexed("pages", site, repo_name, dest)
                    logger.info("Indexed %s: %d chunks", repo_name, len(chunks))
                else:
                    stats["failed"] += 1
                    logger.warning("Failed to clone %s", repo_name)

            except Exception:
                stats["failed"] += 1
                logger.exception("Error processing %s.pages.redhat.com", site)

            if progress and task_id is not None:
                progress.advance(task_id)

    return stats
