"""README.md репозитория, если описание задачи — только ссылка на git.

Не привязываемся к GitHub/GitLab (у пользователя может быть селф-хостед):
пробуем набор известных путей к сырому README — API github/gitlab/gitea
и raw-пути web-интерфейсов — пока какой-нибудь не ответит markdown'ом.
Нашли — отдаём, нет — None (фронт оставляет описание как было).
"""

from __future__ import annotations

import threading
import time
from urllib.parse import urlparse

import httpx

# Общий тайм-бюджет на одну ссылку: перебор кандидатов не дольше этого.
TOTAL_TIMEOUT = 15.0
PER_TRY_TIMEOUT = 5.0
MAX_BYTES = 256_000
CACHE_TTL = 600.0  # повторные чтения одной ссылки не долбят git-хост
_CACHE: dict[str, tuple[float, str | None]] = {}
_CACHE_LOCK = threading.Lock()

_HEADERS = {
    # GitHub API отдаёт сырой README только с raw-мимом; остальным всё равно
    "Accept": "application/vnd.github.raw+json, text/markdown;q=0.9, text/plain;q=0.8, */*;q=0.7",
    "User-Agent": "gntodo/1.0 (README for task description)",
}


def repo_candidates(url: str) -> list[str]:
    """Ссылка на репозиторий → кандидаты URL сырого README.md (пусто — не похоже)."""
    parsed = urlparse(url)
    if parsed.scheme not in ("http", "https") or not parsed.netloc:
        return []
    path = parsed.path.strip("/")
    if path.endswith(".git"):
        path = path[: -len(".git")]
    segments = [s for s in path.split("/") if s]
    # «Похоже на git»: ожидаем owner/repo (вложенность селф-хостеда — тоже git-система,
    # берём последние два сегмента пути как owner/repo)
    if len(segments) < 2:
        return []
    owner, repo = segments[-2], segments[-1]
    base = f"{parsed.scheme}://{parsed.netloc}"
    full_path = "/".join(segments)
    out: list[str] = []
    # raw-путь с HEAD (дефолтная ветка) — самый универсальный: github, GitBucket,
    # Gogs; GitHub API вторым (умеет любой регистр/расширение README, но 60/час
    # без токена), затем API GitLab/Gitea (работают и селф-хостед)
    out.append(f"{base}/{full_path}/raw/HEAD/README.md")
    if parsed.netloc == "github.com":
        out.append(f"https://api.github.com/repos/{owner}/{repo}/readme")
    # GitLab (gitlab.com и селф-хостед): url-encoded путь, HEAD = дефолтная ветка
    quoted = full_path.replace("/", "%2F")
    out.append(
        f"{base}/api/v4/projects/{quoted}/repository/files/README%2Emd/raw?ref=HEAD"
    )
    # Gitea/Gogs API + прочие raw-пути web-интерфейсов
    out.append(f"{base}/api/v1/repos/{owner}/{repo}/raw/README.md?ref=HEAD")
    out.extend(
        [
            f"{base}/{full_path}/-/raw/HEAD/README.md",
            f"{base}/{full_path}/raw/main/README.md",
            f"{base}/{full_path}/raw/master/README.md",
        ]
    )
    return out


def _looks_html(text: str, content_type: str) -> bool:
    if "html" in content_type.lower():
        return True
    head = text.lstrip()[:200].lower()
    return head.startswith("<!doctype html") or head.startswith("<html")


async def _try(client: httpx.AsyncClient, url: str) -> str | None:
    try:
        resp = await client.get(url)
    except httpx.HTTPError:
        return None
    if resp.status_code != 200:
        return None
    text = resp.text
    if not text.strip() or _looks_html(text, resp.headers.get("content-type", "")):
        return None
    return text[:MAX_BYTES]


async def fetch_readme(url: str) -> str | None:
    """Сырой README.md по ссылке на репозиторий; None — не нашли (кэш 10 минут)."""
    now = time.monotonic()
    with _CACHE_LOCK:
        hit = _CACHE.get(url)
        if hit is not None and now - hit[0] < CACHE_TTL:
            return hit[1]
    content: str | None = None
    candidates = repo_candidates(url)
    if candidates:
        started = time.monotonic()
        async with httpx.AsyncClient(follow_redirects=True, timeout=PER_TRY_TIMEOUT) as client:
            for candidate in candidates:
                if time.monotonic() - started > TOTAL_TIMEOUT:
                    break
                content = await _try(client, candidate)
                if content is not None:
                    break
    with _CACHE_LOCK:
        if len(_CACHE) > 128:
            _CACHE.clear()
        _CACHE[url] = (now, content)
    return content
