"""
venvy.core.requirements
~~~~~~~~~~~~~~~~~~~~~~~
Manages requirements.txt — add, remove, sync packages automatically.
"""

from __future__ import annotations

import re
from pathlib import Path
from typing import Dict, List, Optional, Tuple


# Matches "packagename==1.2.3", "packagename>=1.0", bare "packagename", etc.
_PKG_LINE_RE = re.compile(
    r"^(?P<name>[A-Za-z0-9_.\-]+)\s*(?P<spec>[^#\n]*?)?\s*(?:#.*)?$"
)


class RequirementsManager:
    """Read and write a requirements file, tracking packages by name."""

    def __init__(self, filepath: Path) -> None:
        self.filepath = filepath

    @property
    def exists(self) -> bool:
        return self.filepath.exists()

    def create_empty(self) -> None:
        self.filepath.write_text("", encoding="utf-8")

    def _read_lines(self) -> List[str]:
        if not self.exists:
            return []
        return self.filepath.read_text(encoding="utf-8").splitlines()

    def _write_lines(self, lines: List[str]) -> None:
        content = "\n".join(lines)
        if content and not content.endswith("\n"):
            content += "\n"
        self.filepath.write_text(content, encoding="utf-8")

    def _parse(self) -> Tuple[Dict[str, Tuple[str, int]], List[str]]:
        """
        Returns:
            packages: {normalized_name: (original_line, line_index)}
            lines: all lines (including comments, blanks)
        """
        lines = self._read_lines()
        packages: Dict[str, Tuple[str, int]] = {}
        for idx, line in enumerate(lines):
            stripped = line.strip()
            if not stripped or stripped.startswith("#") or stripped.startswith("-"):
                continue
            m = _PKG_LINE_RE.match(stripped)
            if m:
                name = _normalize(m.group("name"))
                packages[name] = (line, idx)
        return packages, lines

    def add_or_update(self, package: str, version: str) -> bool:
        """
        Add or update a package entry. Returns True if file was changed.
        e.g. add_or_update("requests", "2.31.0") → "requests==2.31.0"
        """
        packages, lines = self._parse()
        norm = _normalize(package)
        entry = f"{package}=={version}"

        if norm in packages:
            _, idx = packages[norm]
            if lines[idx].strip() == entry:
                return False  # already up-to-date
            lines[idx] = entry
        else:
            lines.append(entry)

        self._write_lines(lines)
        return True

    def remove(self, package: str) -> bool:
        """Remove a package entry. Returns True if it was present."""
        packages, lines = self._parse()
        norm = _normalize(package)

        if norm not in packages:
            return False

        _, idx = packages[norm]
        lines.pop(idx)
        self._write_lines(lines)
        return True

    def list_packages(self) -> Dict[str, str]:
        """Return {name: version_spec} dict of all packages."""
        packages, lines = self._parse()
        result = {}
        for norm, (line, _) in packages.items():
            m = _PKG_LINE_RE.match(line.strip())
            if m:
                result[m.group("name")] = m.group("spec").strip()
        return result

    def sync_from_installed(self, installed: Dict[str, str]) -> int:
        """
        Overwrite requirements with the current installed packages.
        Returns number of packages written.
        """
        lines = []
        for name, version in sorted(installed.items()):
            lines.append(f"{name}=={version}")
        self._write_lines(lines)
        return len(lines)


def _normalize(name: str) -> str:
    """Normalize package name for comparison (PEP 503)."""
    return re.sub(r"[-_.]+", "-", name).lower()
