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

Enhanced version that uses multiple data sources from the cloudsense repo:
1. cloudsense.map        -- repo-name -> [namespace] annotations
2. .gitmodules           -- repo-name -> GitHub URL mappings
3. apex-packages/README  -- branches, status, R38, customer usage, baselines
4. ecs-services/README   -- cloud service repo mapping
5. eks-services/README   -- cloud service repo mapping
6. docs/TPM/2nd Brain/   -- domain knowledge reference docs

Produces:
- analysis/package-repo-mapping.json  (enriched mapping)
- analysis/version-ref-map.json       (verifiable + editable ref map)
- analysis/reference-docs/            (2nd Brain docs for Phase C)
"""

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,
    clone_sparse,
    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 using rich metadata from the cloudsense repo. "
        "Produces an enriched mapping with branches, deprecation, R38 "
        "status, customer usage, and a verifiable version-ref-map. "
        "Must be called AFTER get_installed_packages."
    ),
    "inputSchema": {
        "type": "object",
        "properties": {
            "working_directory": {
                "type": "string",
                "description": (
                    "Absolute path to the assessment working directory."
                ),
            },
            "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."
                ),
            },
        },
        "required": ["working_directory"],
    },
}

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

# Fallback namespace→repo for packages not in cloudsense.map or where the
# map has a different namespace annotation than the actual Salesforce ns.
# Keys are lowercased namespaces.
_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",
    },
}

# Fallback package-name substring → repo for packages that have no namespace
# or whose namespace isn't sufficient. Keys are lowercased substrings.
_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",
    },
}


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


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

_REFERENCE_FILES = [
    "cloudsense.map",
    ".gitmodules",
    "source-code/apex-packages/README.md",
    "source-code/ecs-services/README.md",
    "source-code/eks-services/README.md",
    ".claude/CLAUDE.md",
    "README.md",
]


def _read_file_safe(path: Path) -> str | None:
    """Read a file if it exists, return None otherwise."""
    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]:
    """Find 2nd Brain MD files, excluding Data Extraction and Upgrades."""
    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)


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).

    Returns (repo_root, error_message).
    """
    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 content -> {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 content -> {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


# ---------------------------------------------------------------------------
# Version-ref resolution
# ---------------------------------------------------------------------------

def _resolve_from_ref(
    readme_pkg: dict[str, Any] | None,
    installed_version: str,
    target_release: str | None,
) -> tuple[str | None, str, str]:
    """Resolve the from-version git ref for a package.

    Returns (ref, ref_type, strategy).
    """
    if readme_pkg:
        baseline = readme_pkg.get("chosen_baseline")
        if baseline and str(baseline).strip().lower() != "tbd":
            return baseline.strip(), "branch", "chosen_baseline"

    release_num = _extract_release_number(installed_version)
    if release_num:
        branch_name = f"R{release_num}"
        branches = (readme_pkg or {}).get("branches", [])
        if branch_name in branches:
            return branch_name, "branch", "release_branch_readme"
        return branch_name, "branch", "inferred_release_branch"

    return None, "unknown", "unresolved"


def _resolve_to_ref(
    readme_pkg: dict[str, Any] | None,
    target_version: str,
    target_release: str | None,
) -> tuple[str | None, str, str]:
    """Resolve the to-version git ref for a package.

    Returns (ref, ref_type, strategy).
    """
    branches = (readme_pkg or {}).get("branches", [])

    if target_version in branches:
        return target_version, "branch", "direct_branch_readme"

    if target_release:
        branch_name = f"R{target_release}"
        if branch_name in branches:
            return branch_name, "branch", "release_branch_readme"
        return branch_name, "branch", "inferred_target_branch"

    return target_version, "branch", "inferred_target"


# ---------------------------------------------------------------------------
# Customer usage tiers
# ---------------------------------------------------------------------------

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


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

async def run(arguments: dict[str, Any]) -> str:
    """Execute the get_package_repo_mapping tool."""
    working_dir = Path(arguments["working_directory"])
    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 (Enhanced)\n")

    # ── 2. Obtain repo root ───────────────────────────────────────
    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

    # ── 3. Read all 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}"

    # ── 4. Parse all 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 apex-packages README")
    report.append(f"Parsed **{len(ecs_services)}** ECS services")
    report.append(f"Parsed **{len(eks_services)}** EKS services\n")

    # Build namespace -> {repo_name, url} from map + gitmodules
    # All namespace keys are lowercased for case-insensitive matching
    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}",
        }

    # Also add repos from README that might not be in cloudsense.map
    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", ""),
            }

    # ── 5. Load installed packages ────────────────────────────────
    current_state = state.load_state(working_dir) or {}
    target_version = current_state.get("target_version", "")
    target_release = _extract_release_number(target_version)
    packages_info = current_state.get("packages_discovered", {})
    packages = packages_info.get("packages", [])
    cs_packages = [p for p in packages if p.get("is_cloudsense")]

    report.append(
        f"Installed CS packages: **{len(cs_packages)}** "
        f"(target: **{target_version}**)\n"
    )

    # ── 6. Build enriched mapping ─────────────────────────────────
    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", "")

        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]
            logger.info("Known fallback for namespace '%s' -> %s", ns, repo_info["repo_name"])

        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
                    logger.info("Known fallback for name '%s' -> %s", name, info["repo_name"])
                    break

        if repo_info:
            entry: dict[str, Any] = {
                "namespace": ns,
                "package_name": name,
                "installed_version": version,
                "repo_name": repo_info["repo_name"],
                "url": repo_info["url"],
            }

            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["chosen_baseline"] = readme_pkg.get(
                    "chosen_baseline", ""
                )
                entry["migration_notes"] = readme_pkg.get(
                    "migration_notes", ""
                )
                entry["migration_complexity"] = readme_pkg.get(
                    "migration_complexity", "unknown"
                )
                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["identical_versions"] = False
                entry["code_empty"] = False

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

            cs_repos = cloud_service_map.get(ns)
            if cs_repos:
                entry["has_cloud_service"] = True
                entry["cloud_service_repos"] = cs_repos
            else:
                entry["has_cloud_service"] = False

            mapped.append(entry)
        else:
            unmapped.append({
                "namespace": ns or "(none)",
                "package_name": name,
                "installed_version": version,
                "repo_name": None,
                "url": None,
                "reason": (
                    "no namespace" if not ns
                    else "not found in cloudsense.map or README"
                ),
            })

    # ── 7. 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]
                entry = {
                    "namespace": ns,
                    "package_name": u["package_name"],
                    "installed_version": u["installed_version"],
                    "repo_name": best["name"],
                    "url": best["url"],
                    "source": "github_search",
                    "search_term": term,
                    "branches": [],
                    "status": "unknown",
                    "deprecated": False,
                    "r38_created": False,
                    "customer_usage": 0,
                    "identical_versions": False,
                    "code_empty": False,
                    "has_cloud_service": False,
                    "usage_tier": "low-impact",
                }
                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"
        )

    # Sort by customer_usage descending (highest impact first)
    mapped.sort(key=lambda x: x.get("customer_usage", 0), reverse=True)

    # ── 8. Build version-ref-map ──────────────────────────────────
    ref_map_entries: list[dict[str, Any]] = []

    for m in mapped:
        ns = m["namespace"]
        readme_pkg = readme_by_namespace.get(ns.lower())
        installed_version = m.get("installed_version", "")

        from_ref, from_type, from_strategy = _resolve_from_ref(
            readme_pkg, installed_version, target_release,
        )
        to_ref, to_type, to_strategy = _resolve_to_ref(
            readme_pkg, target_version, target_release,
        )

        # Determine skip_reason
        skip_reason = None
        if m.get("deprecated"):
            skip_reason = "deprecated"
        elif m.get("code_empty"):
            skip_reason = "no_code"
        elif m.get("identical_versions"):
            skip_reason = "identical_versions"

        # Replacement mapping for deprecated installed packages
        replacement_info = None
        replacement_installed = None
        if m.get("deprecated") and m.get("replacement"):
            repl = m["replacement"]
            repl_ns = repl.get("replaced_by", "")
            replacement_info = repl
            if repl_ns:
                repl_ns_lower = repl_ns.lower()
                replacement_installed = any(
                    (p.get("namespace") or "").lower() == repl_ns_lower
                    for p in cs_packages
                )
                replacement_info["replacement_installed"] = replacement_installed
                if not replacement_installed:
                    warnings.append(
                        f"DEPRECATED PACKAGE {ns}: replacement '{repl_ns}' "
                        f"is NOT installed. It may need to be installed "
                        f"as part of the upgrade."
                    )

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

        ref_entry: dict[str, Any] = {
            "namespace": ns,
            "package_name": m.get("package_name", ""),
            "repo_name": m["repo_name"],
            "url": m["url"],
            "installed_version": installed_version,
            "from_ref": from_ref,
            "from_ref_type": from_type,
            "from_strategy": from_strategy,
            "from_verified": False,
            "to_ref": to_ref,
            "to_ref_type": to_type,
            "to_strategy": to_strategy,
            "to_verified": False,
            "override_from_ref": None,
            "override_to_ref": None,
            "status": m.get("status", "unknown"),
            "deprecated": m.get("deprecated", False),
            "r38_created": m.get("r38_created", False),
            "identical_versions": m.get("identical_versions", False),
            "customer_usage": m.get("customer_usage", 0),
            "usage_tier": m.get("usage_tier", "low-impact"),
            "migration_complexity": m.get("migration_complexity", "unknown"),
            "skip_reason": skip_reason,
            "notes": m.get("migration_notes", ""),
        }

        if replacement_info:
            ref_entry["replacement"] = replacement_info

        if m.get("has_cloud_service"):
            ref_entry["has_cloud_service"] = True
            ref_entry["cloud_service_repos"] = [
                r["repo_name"] for r in m.get("cloud_service_repos", [])
            ]

        ref_map_entries.append(ref_entry)

    # ── 9. 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"
        )

    # ── 10. 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"))

    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")

    # Ref resolution stats
    resolved_both = sum(
        1 for r in ref_map_entries
        if r["from_ref"] and r["to_ref"] and not r.get("skip_reason")
    )
    resolved_from_only = sum(
        1 for r in ref_map_entries
        if r["from_ref"] and not r["to_ref"]
    )
    resolved_to_only = sum(
        1 for r in ref_map_entries
        if not r["from_ref"] and r["to_ref"]
    )
    unresolved = sum(
        1 for r in ref_map_entries
        if not r["from_ref"] and not r["to_ref"] and not r.get("skip_reason")
    )
    skipped = sum(1 for r in ref_map_entries if r.get("skip_reason"))

    report.append("### Version Ref Map Summary\n")
    report.append(f"- **{resolved_both}** packages with both refs resolved")
    report.append(f"- **{resolved_from_only}** with only from-ref")
    report.append(f"- **{resolved_to_only}** with only to-ref")
    report.append(f"- **{unresolved}** unresolved")
    report.append(f"- **{skipped}** skipped (deprecated/no-code/identical)\n")

    if mapped:
        report.append("### Top Packages by Customer Usage\n")
        report.append("| NS | Package | Usage | Tier | From | To | Status |")
        report.append("|-----|---------|-------|------|------|----|--------|")
        for m in mapped[:15]:
            ns = m["namespace"]
            ref_e = next(
                (r for r in ref_map_entries if r["namespace"] == ns), {}
            )
            from_ref = ref_e.get("from_ref") or "?"
            to_ref = ref_e.get("to_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"| {ns} | {m.get('package_name', '')[:30]} | "
                f"{m.get('customer_usage', 0)} | {m.get('usage_tier', '')} | "
                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['installed_version']} | {u['reason']} |"
            )

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

    mapping_file = output_dir / "package-repo-mapping.json"
    mapping_file.write_text(json.dumps({
        "target_version": target_version,
        "target_release": target_release,
        "mapped_count": len(mapped),
        "unmapped_count": len(unmapped),
        "deprecated_count": dep_count,
        "identical_count": ident_count,
        "cloud_service_count": cloud_count,
        "mapped": mapped,
        "unmapped": unmapped,
    }, indent=2))

    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",
        "packages": ref_map_entries,
    }, indent=2))

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

    # ── 12. 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["repo_name"],
                    "url": m["url"],
                    "installed_version": m["installed_version"],
                }
                for m in mapped
            ],
        },
        version_ref_map={
            "target_version": target_version,
            "total": len(ref_map_entries),
            "resolved_both": resolved_both,
            "resolved_from_only": resolved_from_only,
            "resolved_to_only": resolved_to_only,
            "unresolved": unresolved,
            "overridden": 0,
            "verified": 0,
            "skipped": skipped,
        },
    )

    # ── 13. 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 resolved\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)
