"""
Release management utilities for FMUS-VID.

This module provides tools for managing releases, versioning, and changelogs.
"""

import re
import subprocess
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
import tomli
import tomli_w
import logging
import git
from packaging import version

logger = logging.getLogger(__name__)

@dataclass
class ChangelogEntry:
    """Single changelog entry."""
    type: str  # added, changed, deprecated, removed, fixed, security
    description: str
    issue_number: Optional[int] = None
    breaking: bool = False

@dataclass
class Release:
    """Release information."""
    version: str
    date: datetime
    changes: List[ChangelogEntry]
    description: Optional[str] = None

class VersionManager:
    """
    Manages package versioning using semantic versioning.
    """

    def __init__(self, project_root: Path):
        """
        Initialize version manager.

        Args:
            project_root: Root directory of the project
        """
        self.project_root = Path(project_root)
        self.pyproject_path = self.project_root / "pyproject.toml"

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

        Returns:
            Current version string
        """
        with open(self.pyproject_path, "rb") as f:
            pyproject = tomli.load(f)
        return pyproject["project"]["version"]

    def bump_version(
        self,
        bump_type: str = "patch",
        prerelease: Optional[str] = None
    ) -> str:
        """
        Bump package version.

        Args:
            bump_type: Type of version bump (major, minor, patch)
            prerelease: Prerelease identifier (alpha, beta, rc)

        Returns:
            New version string
        """
        current = version.parse(self.get_current_version())

        # Menghitung versi baru
        if bump_type == "major":
            new_version = version.Version(f"{current.major + 1}.0.0")
        elif bump_type == "minor":
            new_version = version.Version(f"{current.major}.{current.minor + 1}.0")
        else:  # patch
            new_version = version.Version(
                f"{current.major}.{current.minor}.{current.micro + 1}"
            )

        # Menambahkan prerelease jika ada
        if prerelease:
            new_version = version.Version(f"{new_version}a1")

        # Memperbarui pyproject.toml
        with open(self.pyproject_path, "rb") as f:
            pyproject = tomli.load(f)

        pyproject["project"]["version"] = str(new_version)

        with open(self.pyproject_path, "wb") as f:
            tomli_w.dump(pyproject, f)

        return str(new_version)

    def validate_version(self, version_str: str) -> bool:
        """
        Validate version string.

        Args:
            version_str: Version string to validate

        Returns:
            True if valid, False otherwise
        """
        try:
            version.parse(version_str)
            return True
        except version.InvalidVersion:
            return False

class ChangelogManager:
    """
    Manages changelog generation and updates.
    """

    def __init__(self, project_root: Path):
        """
        Initialize changelog manager.

        Args:
            project_root: Root directory of the project
        """
        self.project_root = Path(project_root)
        self.changelog_path = self.project_root / "CHANGELOG.md"

    def add_entry(self, entry: ChangelogEntry) -> None:
        """
        Add new changelog entry.

        Args:
            entry: Changelog entry to add
        """
        current_content = self.changelog_path.read_text() if self.changelog_path.exists() else ""

        # Membuat entri baru
        new_entry = self._format_entry(entry)

        # Menambahkan ke bagian Unreleased
        if "## [Unreleased]" in current_content:
            content = current_content.replace(
                "## [Unreleased]",
                f"## [Unreleased]\n\n{new_entry}"
            )
        else:
            content = f"# Changelog\n\n## [Unreleased]\n\n{new_entry}\n\n{current_content}"

        self.changelog_path.write_text(content)

    def create_release(self, version_str: str, date: Optional[datetime] = None) -> None:
        """
        Create new release section in changelog.

        Args:
            version_str: Version string for release
            date: Release date (defaults to current date)
        """
        date = date or datetime.now()
        current_content = self.changelog_path.read_text()

        # Memindahkan entri dari Unreleased ke release baru
        unreleased_pattern = r"## \[Unreleased\]\n\n(.*?)(?=\n## \[|$)"
        match = re.search(unreleased_pattern, current_content, re.DOTALL)

        if match:
            unreleased_content = match.group(1).strip()
            release_section = (
                f"## [{version_str}] - {date.strftime('%Y-%m-%d')}\n\n"
                f"{unreleased_content}\n"
            )

            content = current_content.replace(
                match.group(0),
                f"## [Unreleased]\n\n{release_section}"
            )

            self.changelog_path.write_text(content)

    def _format_entry(self, entry: ChangelogEntry) -> str:
        """Format changelog entry."""
        prefix = "### "
        if entry.breaking:
            prefix += "⚠️ "

        text = f"- {entry.description}"
        if entry.issue_number:
            text += f" (#{entry.issue_number})"

        return f"{prefix}{entry.type.title()}\n{text}\n"

class ReleaseManager:
    """
    Manages the release process.
    """

    def __init__(self, project_root: Path):
        """
        Initialize release manager.

        Args:
            project_root: Root directory of the project
        """
        self.project_root = Path(project_root)
        self.version_manager = VersionManager(project_root)
        self.changelog_manager = ChangelogManager(project_root)

    def prepare_release(
        self,
        version_str: Optional[str] = None,
        bump_type: str = "patch"
    ) -> None:
        """
        Prepare a new release.

        Args:
            version_str: Specific version to release
            bump_type: Type of version bump if no version specified
        """
        # Memvalidasi status git
        repo = git.Repo(self.project_root)
        if repo.is_dirty():
            raise ValueError("Working directory is not clean")

        # Mendapatkan atau memperbarui versi
        if version_str:
            if not self.version_manager.validate_version(version_str):
                raise ValueError(f"Invalid version: {version_str}")
            new_version = version_str
        else:
            new_version = self.version_manager.bump_version(bump_type)

        # Memperbarui changelog
        self.changelog_manager.create_release(new_version)

        # Membuat commit
        repo.index.add([
            str(self.version_manager.pyproject_path),
            str(self.changelog_manager.changelog_path)
        ])
        repo.index.commit(f"Release version {new_version}")

        # Membuat tag
        repo.create_tag(f"v{new_version}")

    def build_release(self) -> None:
        """Build release artifacts."""
        # Membersihkan build sebelumnya
        build_dir = self.project_root / "dist"
        if build_dir.exists():
            for file in build_dir.glob("*"):
                file.unlink()

        # Membangun distribusi
        subprocess.run(
            ["python", "-m", "build", "."],
            cwd=self.project_root,
            check=True
        )

    def publish_release(self, repository: str = "pypi") -> None:
        """
        Publish release to package repository.

        Args:
            repository: Target repository ("pypi" or "testpypi")
        """
        command = ["python", "-m", "twine", "upload"]

        if repository == "testpypi":
            command.extend(["--repository", "testpypi"])

        command.extend([str(p) for p in (self.project_root / "dist").glob("*")])

        subprocess.run(command, check=True)

    def create_github_release(
        self,
        token: str,
        body: Optional[str] = None
    ) -> None:
        """
        Create GitHub release.

        Args:
            token: GitHub API token
            body: Release description
        """
        version = self.version_manager.get_current_version()
        repo = git.Repo(self.project_root)

        # Mendapatkan remote URL
        remote_url = next(repo.remote().urls)
        repo_name = re.search(r"github\.com[:/](.+?)(?:\.git)?$", remote_url).group(1)

        # Membuat release menggunakan GitHub API
        from github import Github
        gh = Github(token)
        repo = gh.get_repo(repo_name)

        if not body:
            # Menggunakan changelog sebagai deskripsi
            body = self._get_version_changelog(version)

        repo.create_git_release(
            f"v{version}",
            f"Release {version}",
            body,
            draft=False,
            prerelease=False
        )

    def _get_version_changelog(self, version_str: str) -> str:
        """Get changelog content for specific version."""
        content = self.changelog_manager.changelog_path.read_text()
        pattern = rf"## \[{version_str}\].*?\n\n(.*?)(?=\n## \[|$)"
        match = re.search(pattern, content, re.DOTALL)
        return match.group(1).strip() if match else ""

def create_release(
    project_root: Path,
    version: Optional[str] = None,
    bump_type: str = "patch",
    github_token: Optional[str] = None
) -> None:
    """
    Create and publish a new release.

    Args:
        project_root: Project root directory
        version: Specific version to release
        bump_type: Type of version bump if no version specified
        github_token: GitHub API token for creating release
    """
    manager = ReleaseManager(project_root)

    try:
        # Mempersiapkan release
        manager.prepare_release(version, bump_type)

        # Membangun distribusi
        manager.build_release()

        # Mempublikasikan ke PyPI
        manager.publish_release()

        # Membuat GitHub release jika token tersedia
        if github_token:
            manager.create_github_release(github_token)

        logger.info(f"Successfully released version {manager.version_manager.get_current_version()}")

    except Exception as e:
        logger.error(f"Release failed: {e}")
        raise
