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

Uses two data sources from the main cloudsense.git repository:
1. cloudsense.map  -- contains repo-name -> [namespace] annotations
2. .gitmodules     -- contains repo-name -> GitHub URL mappings

Cross-references these with installed packages from the state file
to produce a complete namespace -> {repo, url, version} mapping
that downstream tools (clone_package_repos, analyze) can use.
"""

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

from ..utils import state, paths
from ..utils.git_cli import clone_sparse, GitCliError

logger = logging.getLogger(__name__)

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

TOOL_DEFINITION = {
    "name": "get_package_repo_mapping",
    "description": (
        "Maps installed CloudSense packages to their GitHub source repositories. "
        "Clones the main cloudsense repo (sparse checkout -- only mapping files), "
        "parses .gitmodules and cloudsense.map to build namespace->repo mapping, "
        "then cross-references with installed packages from the state file. "
        "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."
                ),
            },
        },
        "required": ["working_directory"],
    },
}

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


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


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

    for line in gitmodules_path.read_text().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


def _obtain_mapping_files(
    working_dir: Path,
    local_repo_path: str | None,
    report: list[str],
) -> tuple[Path | None, Path | None, str | None]:
    """Get cloudsense.map and .gitmodules, either from local path or clone.

    Returns (map_path, gitmodules_path, error_message).
    """
    if local_repo_path:
        local = Path(local_repo_path)
        map_file = local / "cloudsense.map"
        gitmod_file = local / ".gitmodules"
        if map_file.exists() and gitmod_file.exists():
            report.append(
                f"Using existing local repo at `{local_repo_path}`.\n"
            )
            return map_file, gitmod_file, None
        return None, None, (
            f"Local repo path provided but missing mapping files. "
            f"Expected cloudsense.map and .gitmodules in `{local_repo_path}`."
        )

    clone_dir = working_dir / "package-repos" / "cloudsense-main"
    map_file = clone_dir / "cloudsense.map"
    gitmod_file = clone_dir / ".gitmodules"

    if map_file.exists() and gitmod_file.exists():
        report.append("Using existing cloudsense-main clone.\n")
        return map_file, gitmod_file, None

    report.append("Cloning main cloudsense repo (sparse checkout)...\n")
    paths.ensure_dir(clone_dir.parent)

    try:
        clone_sparse(
            CLOUDSENSE_REPO_URL,
            clone_dir,
            files=["cloudsense.map", ".gitmodules"],
            timeout=120,
        )
        report.append("Clone complete.\n")
        return map_file, gitmod_file, None
    except GitCliError as e:
        return None, 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."
        )


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

    # ── 2. Obtain mapping files ───────────────────────────────────
    map_file, gitmod_file, error = _obtain_mapping_files(
        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 map_file is not None and gitmod_file is not None

    # ── 3. Parse both mapping sources ─────────────────────────────
    repo_to_namespace = _parse_cloudsense_map(map_file)
    repo_to_url = _parse_gitmodules(gitmod_file)

    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] = {
            "repo_name": repo_name,
            "url": url or f"https://github.com/trilogy-group/{repo_name}",
        }

    report.append(
        f"Found **{len(repo_to_namespace)}** repos in cloudsense.map\n"
    )
    report.append(
        f"Found **{len(repo_to_url)}** apex-package submodules in .gitmodules\n"
    )

    # ── 4. Cross-reference with installed packages ─────────────────
    current_state = state.load_state(working_dir) or {}
    packages_info = current_state.get("packages_discovered", {})
    packages = packages_info.get("packages", [])

    cs_packages = [p for p in packages if p.get("is_cloudsense")]

    mapped: list[dict[str, Any]] = []
    unmapped: list[dict[str, Any]] = []

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

        if ns and ns in namespace_to_repo:
            entry = {
                "namespace": ns,
                "package_name": name,
                "installed_version": version,
                "repo_name": namespace_to_repo[ns]["repo_name"],
                "url": namespace_to_repo[ns]["url"],
            }
            mapped.append(entry)
        else:
            entry = {
                "namespace": ns or "(none)",
                "package_name": name,
                "installed_version": version,
                "repo_name": None,
                "url": None,
                "reason": (
                    "no namespace" if not ns
                    else "no matching namespace in cloudsense.map"
                ),
            }
            unmapped.append(entry)

    # ── 5. 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\n")

    if mapped:
        report.append("#### Mapped Packages\n")
        report.append("| Namespace | Package | Repo | Version |")
        report.append("|-----------|---------|------|---------|")
        for m in sorted(mapped, key=lambda x: x["namespace"]):
            report.append(
                f"| {m['namespace']} | {m['package_name']} | "
                f"{m['repo_name']} | {m['installed_version']} |"
            )

    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']} |"
            )
        warnings.append(
            f"{len(unmapped)} CS package(s) could not be mapped to source repos. "
            "These may be pre-built ISV packages, have no namespace, or use "
            "non-standard naming."
        )

    # ── 6. Write mapping file ─────────────────────────────────────
    output_dir = paths.ensure_dir(working_dir / "analysis")
    mapping_file = output_dir / "package-repo-mapping.json"
    mapping_file.write_text(json.dumps({
        "mapped": mapped,
        "unmapped": unmapped,
        "repo_count_in_map": len(repo_to_namespace),
        "submodule_count": len(repo_to_url),
    }, indent=2))

    report.append(f"\n### Output\n")
    report.append(f"- Mapping file: `{mapping_file}`")

    # ── 7. Update state ───────────────────────────────────────────
    state.mark_step_completed(working_dir, "get_repo_mapping")
    state.update_state(
        working_dir,
        repo_mapping={
            "mapped": len(mapped),
            "unmapped": len(unmapped),
            "repos_available": len(repo_to_namespace),
            "packages": [
                {
                    "namespace": m["namespace"],
                    "repo_name": m["repo_name"],
                    "url": m["url"],
                    "installed_version": m["installed_version"],
                }
                for m in mapped
            ],
        },
    )

    # ── 8. 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 to source repositories.\n\n"
        "**Next step:** call `clone_package_repos` to clone the repos "
        "for from-version and to-version comparison."
    )

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

    return "\n".join(report)
