# retoor <retoor@molodetz.nl>
from __future__ import annotations

import asyncio
import ipaddress
import logging
import re
import socket
from dataclasses import dataclass
from urllib.parse import urlparse

from devplacepy.services.jobs.isslop.config import GIT_PROBE_TIMEOUT_SECONDS

logger = logging.getLogger(__name__)

KIND_GIT: str = "git"
KIND_WEBSITE: str = "website"

GIT_URL_PATTERN = re.compile(r"(\.git$|^git://|^ssh://git@|^git@)", re.IGNORECASE)
GIT_HOST_HINTS: tuple[str, ...] = ("github.com", "gitlab.com", "bitbucket.org", "codeberg.org", "gitea.")


@dataclass(frozen=True)
class SourceResolution:
    url: str
    kind: str
    probe_output: str


def normalize_url(url: str) -> str:
    url = url.strip()
    if re.match(r"^git@[\w.-]+:", url):
        return url
    if not re.match(r"^[a-z]+://", url, re.IGNORECASE):
        url = f"https://{url}"
    return url


def is_private_host(url: str) -> bool:
    parsed = urlparse(url if "://" in url else f"ssh://{url}")
    host = parsed.hostname or ""
    if not host:
        return True
    if host in ("localhost",):
        return True
    try:
        address = ipaddress.ip_address(host)
        return address.is_private or address.is_loopback or address.is_link_local or address.is_reserved
    except ValueError:
        pass
    try:
        resolved = socket.getaddrinfo(host, None)
    except socket.gaierror:
        return False
    for entry in resolved:
        candidate = ipaddress.ip_address(entry[4][0])
        if candidate.is_private or candidate.is_loopback or candidate.is_link_local:
            return True
    return False


async def probe_git(url: str) -> tuple[bool, str]:
    argv = ["git", "ls-remote", "--heads", "--exit-code", url]
    logger.info("Probing for git repository: %s", url)
    try:
        proc = await asyncio.create_subprocess_exec(
            *argv,
            stdin=asyncio.subprocess.DEVNULL,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
            env={"GIT_TERMINAL_PROMPT": "0", "GIT_ASKPASS": "true", "PATH": "/usr/bin:/bin:/usr/local/bin"},
        )
    except OSError as error:
        logger.error("git probe spawn failed: %s", error)
        return False, str(error)
    try:
        stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=GIT_PROBE_TIMEOUT_SECONDS)
    except asyncio.TimeoutError:
        proc.kill()
        await proc.wait()
        logger.warning("git probe timed out for %s", url)
        return False, "probe timeout"
    output = stdout.decode("utf-8", errors="replace")
    errors = stderr.decode("utf-8", errors="replace")
    is_git = proc.returncode == 0 and bool(output.strip())
    logger.info("git probe %s: is_git=%s refs=%d", url, is_git, len(output.splitlines()))
    return is_git, output if is_git else errors[:500]


async def resolve_source(url: str) -> SourceResolution:
    normalized = normalize_url(url)
    hinted = bool(GIT_URL_PATTERN.search(normalized)) or any(hint in normalized.lower() for hint in GIT_HOST_HINTS)
    is_git, probe_output = await probe_git(normalized)
    if is_git:
        return SourceResolution(url=normalized, kind=KIND_GIT, probe_output=probe_output)
    if hinted:
        logger.info("URL hinted git but probe failed, treating as website: %s", normalized)
    return SourceResolution(url=normalized, kind=KIND_WEBSITE, probe_output=probe_output)
