"""get_package_repo_mapping -- Maps CS packages to GitHub repos.

Two-phase approach:
  Phase 1: Build version mapping from License Portal data in state
           (from_version, to_version, seasonal releases)
  Phase 2: Resolve each package to a GitHub repo using the cloudsense
           meta-repo (cloudsense.map, .gitmodules, READMEs)

Produces a single output file:
  analysis/version-ref-map.json

This replaces the previous two-file approach (package-repo-mapping.json
+ version-ref-map.json).

Data sources:
1. lma_customer_data.packages from state (enriched by get_customer_licenses)
2. cloudsense.map        -- repo-name -> [namespace] annotations
3. .gitmodules           -- repo-name -> GitHub URL mappings
4. apex-packages/README  -- branches, status, R38, customer usage, baselines
5. ecs-services/README   -- cloud service repo mapping
6. eks-services/README   -- cloud service repo mapping
7. docs/TPM/2nd Brain/   -- domain knowledge reference docs
"""

import json
import logging
import re
import shutil
from pathlib import Path
from typing import Any

from ..utils import state, paths
from ..utils.git_cli import (
    clone_shallow,
    github_search_repo,
    GitCliError,
)
from ..utils.readme_parser import (
    parse_apex_packages_readme,
    parse_cloud_services_readme,
    build_cloud_service_map,
)

logger = logging.getLogger(__name__)

CLOUDSENSE_REPO_URL = "https://github.com/trilogy-group/cloudsense.git"

TOOL_DEFINITION = {
    "name": "get_repo_mapping",
    "description": (
        "Maps installed CloudSense packages to their GitHub source "
        "repositories. Phase 1: builds version mapping from License Portal "
        "data (from/to versions, seasonal releases). Phase 2: resolves "
        "repos using the cloudsense meta-repo (branches, deprecation, R38 "
        "status, migration complexity). Produces a single version-ref-map "
        "JSON. Must be called AFTER get_installed_packages."
    ),
    "inputSchema": {
        "type": "object",
        "properties": {
            "cloudsense_repo_path": {
                "type": "string",
                "description": (
                    "Optional. Path to an existing local clone of the "
                    "trilogy-group/cloudsense repo. If provided, skips "
                    "cloning and reads files directly."
                ),
            },
            "working_directory": {
                "type": "string",
                "description": (
                    "Workspace directory for state and output files. "
                    "The AI passes this automatically."
                ),
            },
        },
        "required": [],
    },
}

MAP_ENTRY_RE = re.compile(
    r"^\s*-\s+(cloudsense-[\w-]+)\s+#.*?\[(\w+)\]"
)

_KNOWN_NAMESPACE_REPOS: dict[str, dict[str, str]] = {
    "cfgug1": {
        "repo_name": "cloudsense-cfg-configuration-upgrader",
        "url": "https://github.com/trilogy-group/cloudsense-cfg-configuration-upgrader",
    },
    "cspduia": {
        "repo_name": "cloudsense-product-explorer-designer",
        "url": "https://github.com/trilogy-group/cloudsense-product-explorer-designer",
    },
    "cspofa": {
        "repo_name": "cloudsense-orchestrator",
        "url": "https://github.com/trilogy-group/cloudsense-orchestrator",
    },
    "cscap": {
        "repo_name": "cloudsense-clickapprove",
        "url": "https://github.com/trilogy-group/cloudsense-clickapprove",
    },
}

_KNOWN_NAME_REPOS: dict[str, dict[str, str]] = {
    "e-commerce telco extras": {
        "repo_name": "cloudsense-ecommerce-force-telco-extras",
        "url": "https://github.com/trilogy-group/cloudsense-ecommerce-force-telco-extras",
    },
    "e-commerce extras": {
        "repo_name": "cloudsense-ecommerce-force-extras",
        "url": "https://github.com/trilogy-group/cloudsense-ecommerce-force-extras",
    },
    "ecommerce extras": {
        "repo_name": "cloudsense-ecommerce-force-extras",
        "url": "https://github.com/trilogy-group/cloudsense-ecommerce-force-extras",
    },
    "solution rules designer": {
        "repo_name": "cloudsense-solution-rule-designer",
        "url": "https://github.com/trilogy-group/cloudsense-solution-rule-designer",
    },
    "solution rule designer": {
        "repo_name": "cloudsense-solution-rule-designer",
        "url": "https://github.com/trilogy-group/cloudsense-solution-rule-designer",
    },
    "product explorer": {
        "repo_name": "cloudsense-product-explorer-designer",
        "url": "https://github.com/trilogy-group/cloudsense-product-explorer-designer",
    },
    "cross product rules": {
        "repo_name": "cloudsense-cross-product-rules",
        "url": "https://github.com/trilogy-group/cloudsense-cross-product-rules",
    },
}

_SEASONAL_RELEASE_RE = re.compile(r"R(\d+)", re.IGNORECASE)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _extract_seasonal_release(version_name: str) -> str:
    """Extract seasonal release label from a version name.

    Tries patterns like "R38 GA", "R37.1", "R38-patch", etc.
    Falls back to empty string.
    """
    if not version_name:
        return ""
    m = _SEASONAL_RELEASE_RE.search(version_name)
    return f"R{m.group(1)}" if m else ""


def _extract_release_number(version_str: str) -> str | None:
    """Extract the release train number from a version string."""
    version_str = version_str.strip()
    m = re.match(r"^R(\d+)", version_str, re.IGNORECASE)
    if m:
        return m.group(1)
    parts = version_str.split(".")
    if len(parts) >= 2:
        for part in parts:
            num = int(part) if part.isdigit() else None
            if num is not None and 33 <= num <= 99:
                return str(num)
    return None


def _guess_tag_pattern(version: str) -> str | None:
    """Generate a best-guess git tag from a version number.

    E.g., '1.254.0.1' -> 'v1.254' (strip SF build suffix).
    """
    if not version:
        return None
    parts = version.split(".")
    if len(parts) >= 2:
        return f"v{parts[0]}.{parts[1]}"
    return None


def _usage_tier(count: int) -> str:
    if count >= 30:
        return "critical"
    if count >= 10:
        return "standard"
    return "low-impact"


def _read_file_safe(path: Path) -> str | None:
    if path.exists():
        try:
            return path.read_text(encoding="utf-8", errors="replace")
        except OSError:
            return None
    return None


def _find_brain_docs(repo_root: Path) -> list[Path]:
    brain_dir = repo_root / "docs" / "TPM" / "2nd Brain"
    if not brain_dir.exists():
        return []
    result = []
    for md in brain_dir.rglob("*.md"):
        rel = md.relative_to(brain_dir)
        parts = rel.parts
        if any(p.lower() in ("data extraction", "upgrades") for p in parts):
            continue
        result.append(md)
    return sorted(result)


# ---------------------------------------------------------------------------
# File acquisition
# ---------------------------------------------------------------------------

def _obtain_repo_root(
    working_dir: Path,
    local_repo_path: str | None,
    report: list[str],
) -> tuple[Path | None, str | None]:
    """Get the root of the cloudsense repo (local or cloned)."""
    if local_repo_path:
        local = Path(local_repo_path)
        if (local / "cloudsense.map").exists():
            report.append(f"Using local repo at `{local_repo_path}`.\n")
            return local, None
        return None, (
            f"Local repo path provided but `cloudsense.map` not found "
            f"in `{local_repo_path}`."
        )

    clone_dir = working_dir / "package-repos" / "cloudsense-main"
    readme_path = clone_dir / "source-code" / "apex-packages" / "README.md"
    if (clone_dir / "cloudsense.map").exists() and readme_path.exists():
        report.append("Using existing cloudsense-main clone.\n")
        return clone_dir, None

    if (clone_dir / "cloudsense.map").exists() and not readme_path.exists():
        report.append(
            "Existing clone is incomplete (missing READMEs) -- re-cloning.\n"
        )
        shutil.rmtree(clone_dir, ignore_errors=True)

    report.append("Cloning cloudsense repo (shallow)...\n")
    paths.ensure_dir(clone_dir.parent)

    try:
        clone_shallow(CLOUDSENSE_REPO_URL, clone_dir, depth=1, timeout=180)
        report.append("Clone complete.\n")
        return clone_dir, None
    except GitCliError as e:
        return None, (
            f"Could not clone cloudsense repo: {e}\n\n"
            "Make sure you have GitHub access to trilogy-group/cloudsense.\n"
            "Alternatively, pass `cloudsense_repo_path` pointing to an "
            "existing local clone."
        )


# ---------------------------------------------------------------------------
# Parsing helpers
# ---------------------------------------------------------------------------

def _parse_cloudsense_map(content: str) -> dict[str, str]:
    """Parse cloudsense.map -> {repo_name: namespace (lowercased)}."""
    mapping = {}
    for line in content.splitlines():
        m = MAP_ENTRY_RE.match(line)
        if m:
            mapping[m.group(1)] = m.group(2).lower()
    return mapping


def _parse_gitmodules(content: str) -> dict[str, str]:
    """Parse .gitmodules -> {repo_name: url} for apex-packages."""
    mapping: dict[str, str] = {}
    current_path: str | None = None
    current_url: str | None = None

    for line in content.splitlines():
        stripped = line.strip()
        if stripped.startswith("[submodule"):
            current_path = None
            current_url = None
        elif stripped.startswith("path = "):
            current_path = stripped.split("=", 1)[1].strip()
        elif stripped.startswith("url = "):
            current_url = stripped.split("=", 1)[1].strip()
            if current_path and current_url:
                if "apex-packages" in current_path:
                    repo_name = current_path.rstrip("/").split("/")[-1]
                    url = current_url.rstrip("/")
                    if url.endswith(".git"):
                        url = url[:-4]
                    mapping[repo_name] = url

    return mapping


# ---------------------------------------------------------------------------
# Main tool
# ---------------------------------------------------------------------------

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

    report: list[str] = []
    warnings: list[str] = []

    # ── 0. Prerequisites ─────────────────────────────────────────
    if not state.is_step_completed(working_dir, "get_packages"):
        return (
            "## BLOCKER: get_installed_packages not completed\n\n"
            "You must call get_installed_packages first.\n"
        )

    # ── 1. Resume check ──────────────────────────────────────────
    if state.is_step_completed(working_dir, "get_repo_mapping"):
        current_state = state.load_state(working_dir)
        info = (current_state or {}).get("repo_mapping", {})
        return (
            "## Step already completed\n\n"
            f"Package-to-repo mapping already done. "
            f"Mapped {info.get('mapped', '?')} packages, "
            f"{info.get('unmapped', '?')} unmapped.\n\n"
            "**Next step:** call `clone_package_repos`."
        )

    state.mark_step_started(working_dir, "get_repo_mapping")
    report.append("## Package Repository Mapping\n")

    # ══════════════════════════════════════════════════════════════
    # PHASE 1: Version mapping from License Portal data
    # ══════════════════════════════════════════════════════════════
    report.append("### Phase 1: Version Mapping (from License Portal)\n")

    current_state = state.load_state(working_dir) or {}
    target_version = current_state.get("target_version", "")
    target_release = _extract_release_number(target_version)

    lma_customer_data = current_state.get("lma_customer_data", {})
    lma_packages = lma_customer_data.get("packages", [])

    packages_info = current_state.get("packages_discovered", {})
    cs_packages = [
        p for p in packages_info.get("packages", [])
        if p.get("is_cloudsense")
    ]

    lma_by_name: dict[str, dict[str, Any]] = {}
    lma_by_ns: dict[str, dict[str, Any]] = {}
    for pkg in lma_packages:
        name = pkg.get("name", "")
        ns = (pkg.get("namespace") or "").lower()
        if name:
            lma_by_name[name.lower()] = pkg
        if ns:
            lma_by_ns[ns] = pkg

    report.append(f"- Target version: **{target_version}**")
    report.append(
        f"- License Portal packages: **{len(lma_packages)}**"
    )
    report.append(f"- CS packages to map: **{len(cs_packages)}**\n")

    # ══════════════════════════════════════════════════════════════
    # PHASE 2: Repo resolution from cloudsense meta-repo
    # ══════════════════════════════════════════════════════════════
    report.append("### Phase 2: Repo Resolution (from cloudsense meta-repo)\n")

    repo_root, error = _obtain_repo_root(
        working_dir, local_repo_path, report,
    )
    if error:
        state.mark_step_failed(working_dir, "get_repo_mapping", error)
        return f"## FAILED\n\n{error}"
    assert repo_root is not None

    # Read reference files
    map_content = _read_file_safe(repo_root / "cloudsense.map")
    gitmod_content = _read_file_safe(repo_root / ".gitmodules")
    apex_readme = _read_file_safe(
        repo_root / "source-code" / "apex-packages" / "README.md"
    )
    ecs_readme = _read_file_safe(
        repo_root / "source-code" / "ecs-services" / "README.md"
    )
    eks_readme = _read_file_safe(
        repo_root / "source-code" / "eks-services" / "README.md"
    )

    files_loaded = []
    if map_content:
        files_loaded.append("cloudsense.map")
    if gitmod_content:
        files_loaded.append(".gitmodules")
    if apex_readme:
        files_loaded.append("apex-packages/README.md")
    if ecs_readme:
        files_loaded.append("ecs-services/README.md")
    if eks_readme:
        files_loaded.append("eks-services/README.md")

    report.append(f"Loaded reference files: {', '.join(files_loaded)}\n")

    if not map_content:
        err = "cloudsense.map not found -- cannot build repo mapping."
        state.mark_step_failed(working_dir, "get_repo_mapping", err)
        return f"## FAILED\n\n{err}"

    # Parse sources
    repo_to_namespace = _parse_cloudsense_map(map_content)
    repo_to_url = _parse_gitmodules(gitmod_content) if gitmod_content else {}

    readme_packages: list[dict[str, Any]] = []
    if apex_readme:
        readme_packages = parse_apex_packages_readme(apex_readme)

    ecs_services: list[dict[str, Any]] = []
    eks_services: list[dict[str, Any]] = []
    if ecs_readme:
        ecs_services = parse_cloud_services_readme(ecs_readme, "ecs")
    if eks_readme:
        eks_services = parse_cloud_services_readme(eks_readme, "eks")

    cloud_service_map = build_cloud_service_map(ecs_services, eks_services)

    readme_by_namespace: dict[str, dict[str, Any]] = {}
    readme_by_repo: dict[str, dict[str, Any]] = {}
    for rp in readme_packages:
        readme_by_namespace[rp["namespace"].lower()] = rp
        readme_by_repo[rp["repo_name"]] = rp

    report.append(f"Parsed **{len(repo_to_namespace)}** repos from cloudsense.map")
    report.append(f"Parsed **{len(repo_to_url)}** submodules from .gitmodules")
    report.append(f"Parsed **{len(readme_packages)}** packages from README\n")

    # Build namespace -> repo lookup
    namespace_to_repo: dict[str, dict[str, str]] = {}
    for repo_name, namespace in repo_to_namespace.items():
        url = repo_to_url.get(repo_name, "")
        namespace_to_repo[namespace.lower()] = {
            "repo_name": repo_name,
            "url": url or f"https://github.com/trilogy-group/{repo_name}",
        }

    for rp in readme_packages:
        ns = rp["namespace"].lower()
        if ns not in namespace_to_repo:
            namespace_to_repo[ns] = {
                "repo_name": rp["repo_name"],
                "url": rp.get("url", ""),
            }

    # ── Build consolidated entries ────────────────────────────────
    mapped: list[dict[str, Any]] = []
    unmapped: list[dict[str, Any]] = []

    for pkg in cs_packages:
        ns = pkg.get("namespace") or ""
        ns_lower = ns.lower()
        name = pkg.get("name", "")
        version = pkg.get("version", "")

        lma_pkg = lma_by_ns.get(ns_lower) or lma_by_name.get(name.lower(), {})

        from_version = lma_pkg.get("version", version)
        to_version = lma_pkg.get("latest_version", "")
        from_seasonal = _extract_seasonal_release(
            lma_pkg.get("installed_version_name", "")
        )
        to_seasonal = _extract_seasonal_release(
            lma_pkg.get("latest_version_name", "")
        )

        repo_info = namespace_to_repo.get(ns_lower) if ns else None
        readme_pkg = readme_by_namespace.get(ns_lower) if ns else None

        if not repo_info and ns:
            for rp in readme_packages:
                if rp["namespace"].lower() == ns_lower:
                    repo_info = {"repo_name": rp["repo_name"], "url": rp["url"]}
                    readme_pkg = rp
                    break

        if not repo_info and ns_lower in _KNOWN_NAMESPACE_REPOS:
            repo_info = _KNOWN_NAMESPACE_REPOS[ns_lower]

        if not repo_info and name:
            name_lower = name.lower()
            for substring, info in _KNOWN_NAME_REPOS.items():
                if substring in name_lower:
                    repo_info = info
                    break

        if repo_info:
            from_ref = _guess_tag_pattern(from_version)
            to_ref = _guess_tag_pattern(to_version)

            if not from_ref and readme_pkg:
                baseline = readme_pkg.get("chosen_baseline")
                if baseline and str(baseline).strip().lower() != "tbd":
                    from_ref = baseline.strip()

            if not to_ref and target_release:
                to_ref = f"R{target_release}"

            entry: dict[str, Any] = {
                "package_name": name,
                "namespace": ns,
                "git_repo": repo_info["repo_name"],
                "git_repo_url": repo_info["url"],
                "from_version": from_version,
                "from_seasonal_release": from_seasonal,
                "from_repo_ref": from_ref,
                "from_repo_ref_type": "tag" if from_ref and from_ref.startswith("v") else "branch",
                "from_ref_resolved": False,
                "to_version": to_version,
                "to_seasonal_release": to_seasonal,
                "to_repo_ref": to_ref,
                "to_repo_ref_type": "tag" if to_ref and to_ref.startswith("v") else "branch",
                "to_ref_resolved": False,
                "override_from_ref": None,
                "override_to_ref": None,
            }

            if readme_pkg:
                entry["branches"] = readme_pkg.get("branches", [])
                entry["status"] = readme_pkg.get("status", "unknown")
                entry["deprecated"] = readme_pkg.get("deprecated", False)
                entry["r38_created"] = readme_pkg.get("r38_created", False)
                entry["customer_usage"] = readme_pkg.get("customer_usage", 0)
                entry["migration_complexity"] = readme_pkg.get(
                    "migration_complexity", "unknown"
                )
                entry["migration_notes"] = readme_pkg.get("migration_notes", "")
                entry["identical_versions"] = readme_pkg.get(
                    "identical_versions", False
                )
                entry["code_empty"] = readme_pkg.get("code_empty", False)
                if readme_pkg.get("replacement"):
                    entry["replacement"] = readme_pkg["replacement"]
            else:
                entry["branches"] = []
                entry["status"] = "unknown"
                entry["deprecated"] = False
                entry["r38_created"] = False
                entry["customer_usage"] = 0
                entry["migration_complexity"] = "unknown"
                entry["identical_versions"] = False
                entry["code_empty"] = False

            entry["usage_tier"] = _usage_tier(entry.get("customer_usage", 0))

            skip_reason = None
            if entry.get("deprecated"):
                skip_reason = "deprecated"
            elif entry.get("code_empty"):
                skip_reason = "no_code"
            elif entry.get("identical_versions"):
                skip_reason = "identical_versions"
            entry["skip_reason"] = skip_reason

            cs_repos = cloud_service_map.get(ns)
            entry["has_cloud_service"] = bool(cs_repos)
            if cs_repos:
                entry["cloud_service_repos"] = [
                    r["repo_name"] for r in cs_repos
                ]

            if entry.get("deprecated") and entry.get("replacement"):
                repl = entry["replacement"]
                repl_ns = repl.get("replaced_by", "")
                if repl_ns:
                    repl_installed = any(
                        (p.get("namespace") or "").lower() == repl_ns.lower()
                        for p in cs_packages
                    )
                    repl["replacement_installed"] = repl_installed
                    if not repl_installed:
                        warnings.append(
                            f"DEPRECATED PACKAGE {ns}: replacement "
                            f"'{repl_ns}' is NOT installed."
                        )

            if (
                to_ref
                and not entry.get("r38_created")
                and not entry.get("deprecated")
            ):
                warnings.append(
                    f"{ns}: R38 package NOT yet created in packaging org. "
                    f"Branch may exist but package build may be pending."
                )

            mapped.append(entry)
        else:
            unmapped.append({
                "package_name": name,
                "namespace": ns or "(none)",
                "from_version": from_version,
                "to_version": to_version,
                "git_repo": None,
                "git_repo_url": None,
                "reason": (
                    "no namespace" if not ns
                    else "not found in cloudsense.map or README"
                ),
            })

    # GitHub search fallback for unmapped
    github_resolved: list[dict[str, Any]] = []
    still_unmapped: list[dict[str, Any]] = []

    for u in unmapped:
        ns = u["namespace"]
        if ns == "(none)":
            still_unmapped.append(u)
            continue

        pkg_name = u.get("package_name", "")
        search_terms = [ns]
        if pkg_name:
            name_keywords = pkg_name.replace("CloudSense", "").strip()
            if name_keywords:
                search_terms.append(f"cloudsense {name_keywords}")

        found = False
        for term in search_terms:
            try:
                results = github_search_repo("trilogy-group", term)
            except Exception:
                results = []
            if results:
                best = results[0]
                from_ref = _guess_tag_pattern(u.get("from_version", ""))
                to_ref = _guess_tag_pattern(u.get("to_version", ""))
                entry = {
                    "package_name": u["package_name"],
                    "namespace": ns,
                    "git_repo": best["name"],
                    "git_repo_url": best["url"],
                    "from_version": u.get("from_version", ""),
                    "from_seasonal_release": "",
                    "from_repo_ref": from_ref,
                    "from_repo_ref_type": "tag" if from_ref and from_ref.startswith("v") else "branch",
                    "from_ref_resolved": False,
                    "to_version": u.get("to_version", ""),
                    "to_seasonal_release": "",
                    "to_repo_ref": to_ref,
                    "to_repo_ref_type": "tag" if to_ref and to_ref.startswith("v") else "branch",
                    "to_ref_resolved": False,
                    "override_from_ref": None,
                    "override_to_ref": None,
                    "source": "github_search",
                    "branches": [],
                    "status": "unknown",
                    "deprecated": False,
                    "r38_created": False,
                    "customer_usage": 0,
                    "migration_complexity": "unknown",
                    "identical_versions": False,
                    "code_empty": False,
                    "has_cloud_service": False,
                    "usage_tier": "low-impact",
                    "skip_reason": None,
                }
                mapped.append(entry)
                github_resolved.append(entry)
                found = True
                break

        if not found:
            still_unmapped.append(u)

    unmapped = still_unmapped

    if github_resolved:
        report.append(
            f"GitHub search resolved **{len(github_resolved)}** "
            f"additional packages\n"
        )

    mapped.sort(key=lambda x: x.get("customer_usage", 0), reverse=True)

    # ── Copy 2nd Brain reference docs ─────────────────────────────
    brain_docs = _find_brain_docs(repo_root)
    docs_copied = 0
    if brain_docs:
        ref_docs_dir = paths.ensure_dir(
            working_dir / "analysis" / "reference-docs"
        )
        for doc in brain_docs:
            dest = ref_docs_dir / doc.name
            try:
                shutil.copy2(doc, dest)
                docs_copied += 1
            except OSError as e:
                logger.warning("Failed to copy %s: %s", doc.name, e)

        report.append(
            f"Copied **{docs_copied}** 2nd Brain reference docs "
            f"to `analysis/reference-docs/`\n"
        )

    # ── Render report ─────────────────────────────────────────────
    report.append(f"\n### Results\n")
    report.append(f"- **{len(mapped)}** CS packages mapped to repos")
    report.append(f"- **{len(unmapped)}** CS packages have no repo match")

    dep_count = sum(1 for m in mapped if m.get("deprecated"))
    ident_count = sum(1 for m in mapped if m.get("identical_versions"))
    cloud_count = sum(1 for m in mapped if m.get("has_cloud_service"))
    r38_count = sum(1 for m in mapped if m.get("r38_created"))
    skipped = sum(1 for m in mapped if m.get("skip_reason"))

    report.append(f"- **{dep_count}** deprecated")
    report.append(f"- **{ident_count}** identical from/to versions")
    report.append(f"- **{cloud_count}** have cloud service repos")
    report.append(f"- **{r38_count}** have R38 package created\n")

    resolved_both = sum(
        1 for m in mapped
        if m.get("from_repo_ref") and m.get("to_repo_ref")
        and not m.get("skip_reason")
    )
    unresolved = sum(
        1 for m in mapped
        if not m.get("from_repo_ref") and not m.get("to_repo_ref")
        and not m.get("skip_reason")
    )

    report.append("### Version Ref Summary\n")
    report.append(f"- **{resolved_both}** packages with both refs (preliminary)")
    report.append(f"- **{unresolved}** unresolved")
    report.append(
        f"- **{skipped}** skipped (deprecated/no-code/identical)\n"
    )
    report.append(
        "Note: refs are best-guess tag patterns and will be verified "
        "after repo cloning.\n"
    )

    if mapped:
        report.append("### Top Packages by Customer Usage\n")
        report.append(
            "| NS | Package | From | To | "
            "From Ref | To Ref | Status |"
        )
        report.append(
            "|-----|---------|------|----|"
            "---------|--------|--------|"
        )
        for m in mapped[:15]:
            from_ref = m.get("from_repo_ref") or "?"
            to_ref = m.get("to_repo_ref") or "?"
            status_icon = ""
            if m.get("deprecated"):
                status_icon = "DEPRECATED"
            elif m.get("identical_versions"):
                status_icon = "identical"
            elif m.get("skip_reason"):
                status_icon = m["skip_reason"]
            else:
                status_icon = m.get("migration_complexity", "")
            report.append(
                f"| {m.get('namespace', '')} | "
                f"{m.get('package_name', '')[:30]} | "
                f"{m.get('from_version', '')[:12]} | "
                f"{m.get('to_version', '')[:12]} | "
                f"{from_ref} | {to_ref} | {status_icon} |"
            )

    if unmapped:
        report.append("\n### Unmapped Packages\n")
        report.append("| Namespace | Package | Version | Reason |")
        report.append("|-----------|---------|---------|--------|")
        for u in sorted(unmapped, key=lambda x: x.get("namespace", "")):
            report.append(
                f"| {u['namespace']} | {u['package_name']} | "
                f"{u['from_version']} | {u['reason']} |"
            )

    # ── Write output file ─────────────────────────────────────────
    output_dir = paths.ensure_dir(working_dir / "analysis")

    ref_map_file = output_dir / "version-ref-map.json"
    ref_map_file.write_text(json.dumps({
        "target_version": target_version,
        "target_release": target_release,
        "generated_by": "get_repo_mapping",
        "mapped_count": len(mapped),
        "unmapped_count": len(unmapped),
        "packages": mapped,
        "unmapped": unmapped,
    }, indent=2))

    report.append(f"\n### Output File\n")
    report.append(f"- Version ref map: `{ref_map_file}`")
    if docs_copied:
        report.append(
            f"- Reference docs: "
            f"`{working_dir / 'analysis' / 'reference-docs'}/`"
        )

    # ── Update state ──────────────────────────────────────────────
    state.mark_step_completed(working_dir, "get_repo_mapping")
    state.update_state(
        working_dir,
        repo_mapping={
            "mapped": len(mapped),
            "unmapped": len(unmapped),
            "deprecated": dep_count,
            "r38_ready": r38_count,
            "identical_versions": ident_count,
            "cloud_services": cloud_count,
            "packages": [
                {
                    "namespace": m["namespace"],
                    "repo_name": m["git_repo"],
                    "url": m["git_repo_url"],
                    "from_version": m.get("from_version", ""),
                    "to_version": m.get("to_version", ""),
                }
                for m in mapped
            ],
        },
        version_ref_map={
            "target_version": target_version,
            "total": len(mapped),
            "resolved_both": resolved_both,
            "unresolved": unresolved,
            "skipped": skipped,
        },
    )

    # ── Summary ───────────────────────────────────────────────────
    report.append("\n---\n")
    report.append("## STATUS: READY\n")
    report.append(
        f"Successfully mapped {len(mapped)} out of {len(cs_packages)} "
        f"CloudSense packages.\n\n"
        f"- {resolved_both} packages have both from/to refs (preliminary)\n"
        f"- {skipped} packages will be skipped "
        f"(deprecated/no-code/identical)\n\n"
        "**Review the version-ref-map** at "
        f"`{ref_map_file}` before proceeding.\n"
        "Set `override_from_ref` or `override_to_ref` for any "
        "incorrect auto-resolved refs.\n\n"
        "**Next step:** call `clone_package_repos` to clone repos."
    )

    if warnings:
        report.append("\n## Warnings\n")
        for w in warnings:
            report.append(f"- {w}")

    return "\n".join(report)
