"""Scan a workspace directory for git repos and classify their forge remotes."""

from __future__ import annotations

import fnmatch
import re
import subprocess
from pathlib import Path

from project_hub.core.config import Config
from project_hub.core.models import Repo

# SSH:   git@HOST:PATH.git
# HTTPS: https://HOST/PATH.git  (trailing .git is optional)
_SSH_RE = re.compile(r"^git@([^:]+):(.+?)(?:\.git)?$")
_HTTPS_RE = re.compile(r"^https?://([^/]+)/(.+?)(?:\.git)?$")


def _parse_remote(url: str) -> tuple[str | None, str | None]:
    m = _SSH_RE.match(url)
    if m:
        return m.group(1), m.group(2)
    m = _HTTPS_RE.match(url)
    if m:
        return m.group(1), m.group(2)
    return None, None


def _get_remote_url(repo_path: Path) -> str | None:
    result = subprocess.run(
        ["git", "remote", "get-url", "origin"],
        cwd=repo_path,
        capture_output=True,
        text=True,
        check=False,
    )
    if result.returncode != 0:
        return None
    return result.stdout.strip() or None


_MAX_DEPTH = 10


def _is_scannable_dir(child: Path, config: Config) -> bool:
    """Return True if child is a non-hidden, non-excluded, non-symlink directory."""
    try:
        if not child.is_dir() or child.is_symlink():
            return False
    except (PermissionError, OSError):
        return False
    return not child.name.startswith(".") and child.name not in config.exclude


def _find_git_repos(root: Path, config: Config, depth: int = 0) -> list[tuple[Path, str]]:
    """Recursively find git repos. Returns list of (abs_path, dir_name).

    Stops recursing into a directory once .git is found (no sub-repos).
    Skips hidden directories and excluded repos at every level.
    """
    if depth >= _MAX_DEPTH:
        return []

    try:
        children = sorted(root.iterdir())
    except (PermissionError, OSError):
        return []

    results: list[tuple[Path, str]] = []
    for child in children:
        if not _is_scannable_dir(child, config):
            continue
        try:
            has_git = (child / ".git").exists()
        except (PermissionError, OSError):
            continue
        if has_git:
            results.append((child, child.name))
        else:
            results.extend(_find_git_repos(child, config, depth + 1))

    return results


def _auto_detect_forge_type(host: str) -> str | None:
    """Infer forge type from hostname. Returns None if unrecognized."""
    if "github" in host:
        return "github"
    if "bitbucket" in host:
        return "bitbucket"
    if "gitlab" in host:
        return "gitlab"
    return None


def discover_repos(workspace_root: Path, config: Config) -> list[Repo]:
    """Discover git repos under workspace_root (recursive), applying include/exclude filters."""
    found = _find_git_repos(workspace_root, config)

    # Resolve names: use dir name if unique, otherwise relative path from workspace root
    name_counts: dict[str, int] = {}
    for _, dir_name in found:
        name_counts[dir_name] = name_counts.get(dir_name, 0) + 1

    repos: list[Repo] = []
    for abs_path, dir_name in found:
        if name_counts[dir_name] > 1:
            name = str(abs_path.relative_to(workspace_root))
        else:
            name = dir_name

        if config.include and not any(fnmatch.fnmatch(dir_name, pat) for pat in config.include):
            continue

        remote_url = _get_remote_url(abs_path) or ""
        forge_host, project_path = _parse_remote(remote_url)
        forge_cfg = config.forges.get(forge_host) if forge_host else None
        if forge_cfg:
            forge_type = forge_cfg.get("type")
        elif forge_host:
            forge_type = _auto_detect_forge_type(forge_host)
        else:
            forge_type = None
        has_forge = forge_type is not None

        repos.append(
            Repo(
                name=name,
                path=str(abs_path),
                remote_url=remote_url,
                forge_host=forge_host,
                forge_type=forge_type,
                has_forge=has_forge,
                project_path=project_path,
            )
        )

    return repos
