import os
import json
import time
import hashlib
from pathlib import Path
from typing import Dict, Any, Optional, List


class VersionTagger:
    """
    A robust version tracking system that automatically detects code changes
    and increments the semantic version of a Python module or package.

    This class:
    - Computes hash fingerprints of all `.py` files in a target directory.
    - Detects when any file changes and increments the version automatically.
    - Saves all version history in `.version_info.json`.
    - Supports flexible metadata such as notes, timestamps, and commit hashes.

    Typical use case:
            tagger = VersionTagger(".")
            result = tagger.tag("Fixed data parser bug")
    """

    def __init__(self, module_path: str):
        """
        Initialize the version tagger for a given module path.

        :param module_path: Path to the target module or package.
        """
        self.module_path = Path(module_path).resolve()
        self.version_file = self.module_path / ".version_info.json"
        self.data: Dict[str, Any] = {"version": "1.0.0", "last_hash": "", "history": []}
        if self.version_file.exists():
            self._load()
        else:
            self._save()

    # ------------------------- Internal Utilities -------------------------

    def _load(self) -> None:
        """Load existing version data from the JSON file."""
        with open(self.version_file, "r", encoding="utf-8") as f:
            self.data = json.load(f)

    def _save(self) -> None:
        """Save the current version data to the JSON file."""
        with open(self.version_file, "w", encoding="utf-8") as f:
            json.dump(self.data, f, indent=4)

    def _hash_folder(self) -> str:
        """
        Compute a consistent hash for all Python files in the target directory.
        Uses MD5 for performance; suitable for change detection (not cryptographic security).
        """
        hash_obj = hashlib.md5()
        for file in sorted(self.module_path.rglob("*.py")):
            with open(file, "rb") as f:
                hash_obj.update(f.read())
        return hash_obj.hexdigest()

    def _increment_version(self, old_version: str, level: str = "patch") -> str:
        """
        Increment the semantic version number based on the change level.

        :param old_version: Current version (e.g., "1.0.3")
        :param level: One of "major", "minor", or "patch"
        :return: New version string
        """
        major, minor, patch = map(int, old_version.split("."))
        if level == "major":
            major += 1
            minor = patch = 0
        elif level == "minor":
            minor += 1
            patch = 0
        else:
            patch += 1
        return f"{major}.{minor}.{patch}"

    # ------------------------- Core Methods -------------------------

    def tag(self, note: str = "auto update", level: str = "patch") -> Dict[str, Any]:
        """
        Check for code changes and update version if needed.

        :param note: Optional message describing the reason for version change.
        :param level: Version increment level ("major", "minor", "patch").
        :return: A dictionary containing update info:
                         {
                           "updated": bool,
                           "version": str,
                           "time": str,
                           "note": str
                         }
        """
        current_hash = self._hash_folder()
        last_hash = self.data.get("last_hash")

        # No change detected
        if current_hash == last_hash:
            return {
                "updated": False,
                "version": self.data["version"],
                "time": time.strftime("%Y-%m-%d %H:%M:%S"),
                "note": "No changes detected",
            }

        # Increment version
        new_version = self._increment_version(self.data["version"], level)
        timestamp = time.strftime("%Y-%m-%d %H:%M:%S")

        self.data["version"] = new_version
        self.data["last_hash"] = current_hash
        self.data["history"].append(
            {"version": new_version, "note": note, "time": timestamp}
        )
        self._save()

        return {
            "updated": True,
            "version": new_version,
            "time": timestamp,
            "note": note,
        }

    def get_history(self) -> List[Dict[str, str]]:
        """
        Return the complete version history.

        :return: A list of dictionaries representing version updates.
        """
        return self.data.get("history", [])

    def get_current_version(self) -> str:
        """
        Get the current version string.

        :return: Version number (e.g., "1.0.5")
        """
        return self.data["version"]

    def embed_version(self, file: Optional[str] = None) -> bool:
        """
        Embed the current version number into a target file (e.g., __init__.py).

        :param file: Path to file where version should be written.
        :return: True if updated successfully, else False.
        """
        if not file:
            file = self.module_path / "__init__.py"

        try:
            file = Path(file)
            if not file.exists():
                file.write_text(
                    f'__version__ = "{self.get_current_version()}"\n', encoding="utf-8"
                )
                return True

            lines = file.read_text(encoding="utf-8").splitlines()
            found = False
            for i, line in enumerate(lines):
                if line.startswith("__version__"):
                    lines[i] = f'__version__ = "{self.get_current_version()}"'
                    found = True
                    break
            if not found:
                lines.append(f'__version__ = "{self.get_current_version()}"')
            file.write_text("\n".join(lines) + "\n", encoding="utf-8")
            return True
        except Exception:
            return False
