"""Pure Python Helm chart parsing, version bumping, and packaging.

Eliminates the need for the helm CLI for chart operations.
Uses only stdlib (re, tarfile, io, os) — no external dependencies.
"""

from __future__ import annotations

import io
import os
import re
import tarfile
from pathlib import Path


class ChartError(Exception):
    """Raised when a chart operation fails."""


def find_chart_yaml(path: str | None = None) -> str:
    """Find Chart.yaml. Returns absolute path.

    Search order:
    1. If path specified, use it directly (file or directory)
    2. ./Chart.yaml
    3. ./charts/*/Chart.yaml (if exactly one)
    """
    if path:
        p = Path(path)
        if p.is_file() and p.name == "Chart.yaml":
            return str(p.resolve())
        chart_yaml = p / "Chart.yaml"
        if chart_yaml.is_file():
            return str(chart_yaml.resolve())
        raise ChartError(f"Chart.yaml not found at: {path}")

    cwd = Path.cwd()
    if (cwd / "Chart.yaml").is_file():
        return str((cwd / "Chart.yaml").resolve())

    charts = list(cwd.glob("charts/*/Chart.yaml"))
    if len(charts) == 1:
        return str(charts[0].resolve())
    if len(charts) > 1:
        names = [str(c.parent.name) for c in charts]
        raise ChartError(f"Multiple charts found: {', '.join(names)}. Please specify which one.")

    raise ChartError("No Chart.yaml found. Run from a chart directory or specify the path.")


def parse_chart(chart_yaml: str) -> dict:
    """Parse Chart.yaml and return dict with name, version, appVersion, dir, path."""
    try:
        content = Path(chart_yaml).read_text()
    except OSError as e:
        raise ChartError(f"Cannot read {chart_yaml}: {e}")

    name_match = re.search(r"^name:\s*(.+)$", content, re.MULTILINE)
    version_match = re.search(r"^version:\s*(.+)$", content, re.MULTILINE)
    app_version_match = re.search(
        r'^appVersion:\s*["\']?([^"\']+)["\']?$', content, re.MULTILINE
    )

    if not name_match or not version_match:
        raise ChartError(f"Could not parse name/version from {chart_yaml}")

    return {
        "name": name_match.group(1).strip(),
        "version": version_match.group(1).strip(),
        "appVersion": app_version_match.group(1).strip() if app_version_match else None,
        "path": chart_yaml,
        "dir": str(Path(chart_yaml).parent),
    }


def bump_version(version: str, bump_type: str = "patch") -> str:
    """Bump a semver version string."""
    parts = version.split(".")
    if len(parts) != 3:
        return version

    major, minor, patch = int(parts[0]), int(parts[1]), int(parts[2])

    if bump_type == "major":
        return f"{major + 1}.0.0"
    elif bump_type == "minor":
        return f"{major}.{minor + 1}.0"
    else:
        return f"{major}.{minor}.{patch + 1}"


def update_chart_version(
    chart_yaml: str, new_version: str, new_app_version: str | None = None
) -> None:
    """Update version (and optionally appVersion) in Chart.yaml."""
    try:
        content = Path(chart_yaml).read_text()
    except OSError as e:
        raise ChartError(f"Cannot read {chart_yaml}: {e}")

    content = re.sub(
        r"^(version:\s*).+$",
        rf"\g<1>{new_version}",
        content,
        flags=re.MULTILINE,
    )

    if new_app_version:
        content = re.sub(
            r"""^(appVersion:\s*)["\']?[^"\']+["\']?$""",
            rf'\g<1>"{new_app_version}"',
            content,
            flags=re.MULTILINE,
        )

    Path(chart_yaml).write_text(content)


def package_chart(chart_dir: str, version: str | None = None) -> str:
    """Package a Helm chart directory into a .tgz archive.

    Returns the path to the created .tgz file. Pure Python — no helm CLI needed.
    Uses the same layout as `helm package`: {chart_name}-{version}.tgz
    containing {chart_name}/ as the top-level directory.
    """
    chart_yaml = os.path.join(chart_dir, "Chart.yaml")
    info = parse_chart(chart_yaml)
    chart_name = info["name"]
    chart_version = version or info["version"]

    tgz_name = f"{chart_name}-{chart_version}.tgz"
    tgz_path = os.path.join(chart_dir, tgz_name)

    chart_path = Path(chart_dir)

    with tarfile.open(tgz_path, "w:gz") as tar:
        for root, dirs, files in os.walk(chart_dir):
            # Skip hidden dirs, .tgz files, charts/ dependencies (unless Chart.lock)
            dirs[:] = [d for d in dirs if not d.startswith(".")]

            for fname in files:
                if fname.endswith(".tgz"):
                    continue

                filepath = os.path.join(root, fname)
                # Archive name: chart_name/relative_path
                relpath = os.path.relpath(filepath, chart_dir)
                arcname = os.path.join(chart_name, relpath)
                tar.add(filepath, arcname=arcname)

    return tgz_path
