#!/usr/bin/env python3
"""
Semantic Versioning Manager for CHRONOS

Manages version bumping, changelog generation, and git tagging
following semantic versioning (semver) principles.

Usage:
    python scripts/version_manager.py [command] [options]

Commands:
    show              Show current version
    major             Bump major version (breaking changes)
    minor             Bump minor version (new features)
    patch             Bump patch version (bug fixes)
    prerelease        Create prerelease (alpha, beta, rc)
    finalize          Finalize prerelease to stable
    changelog         Generate changelog from git commits
    tag               Create git tag for version
"""

import argparse
import re
import subprocess
import sys
from datetime import datetime
from pathlib import Path
from typing import Optional, Tuple


class SemanticVersion:
    """Manages semantic versioning operations."""
    
    SEMVER_PATTERN = re.compile(
        r'^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)'
        r'(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)'
        r'(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?'
        r'(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'
    )
    
    def __init__(self, version_string: str):
        """Initialize with version string."""
        match = self.SEMVER_PATTERN.match(version_string)
        if not match:
            raise ValueError(f"Invalid semantic version: {version_string}")
        
        self.major = int(match.group('major'))
        self.minor = int(match.group('minor'))
        self.patch = int(match.group('patch'))
        self.prerelease = match.group('prerelease')
        self.buildmetadata = match.group('buildmetadata')
    
    def __str__(self) -> str:
        """Return version string."""
        version = f"{self.major}.{self.minor}.{self.patch}"
        if self.prerelease:
            version += f"-{self.prerelease}"
        if self.buildmetadata:
            version += f"+{self.buildmetadata}"
        return version
    
    def bump_major(self) -> 'SemanticVersion':
        """Bump major version, reset minor and patch."""
        return SemanticVersion(f"{self.major + 1}.0.0")
    
    def bump_minor(self) -> 'SemanticVersion':
        """Bump minor version, reset patch."""
        return SemanticVersion(f"{self.major}.{self.minor + 1}.0")
    
    def bump_patch(self) -> 'SemanticVersion':
        """Bump patch version."""
        return SemanticVersion(f"{self.major}.{self.minor}.{self.patch + 1}")
    
    def create_prerelease(self, prerelease_type: str = 'alpha', number: int = 1) -> 'SemanticVersion':
        """Create prerelease version."""
        valid_types = ['alpha', 'beta', 'rc']
        if prerelease_type not in valid_types:
            raise ValueError(f"Invalid prerelease type: {prerelease_type}")
        
        return SemanticVersion(f"{self.major}.{self.minor}.{self.patch}-{prerelease_type}.{number}")
    
    def finalize_prerelease(self) -> 'SemanticVersion':
        """Remove prerelease suffix."""
        if not self.prerelease:
            raise ValueError("Version is not a prerelease")
        return SemanticVersion(f"{self.major}.{self.minor}.{self.patch}")


def get_current_version(pyproject_path: Path) -> str:
    """Extract version from pyproject.toml."""
    with open(pyproject_path) as f:
        content = f.read()
    
    match = re.search(r'version\s*=\s*["\']([^"\']+)["\']', content)
    if not match:
        raise ValueError("Could not find version in pyproject.toml")
    
    return match.group(1)


def update_version_in_file(file_path: Path, new_version: str) -> None:
    """Update version in file."""
    with open(file_path) as f:
        content = f.read()
    
    # Update in pyproject.toml
    if file_path.name == 'pyproject.toml':
        content = re.sub(
            r'version\s*=\s*["\'][^"\']+["\']',
            f'version = "{new_version}"',
            content
        )
    
    # Update in __init__.py
    elif file_path.name == '__init__.py':
        content = re.sub(
            r'__version__\s*=\s*["\'][^"\']+["\']',
            f'__version__ = "{new_version}"',
            content
        )
    
    with open(file_path, 'w') as f:
        f.write(content)


def get_git_commits(from_ref: Optional[str] = None, to_ref: str = 'HEAD') -> list:
    """Get git commits since last version."""
    try:
        if from_ref:
            cmd = ['git', 'log', f'{from_ref}..{to_ref}', '--oneline']
        else:
            cmd = ['git', 'log', to_ref, '--oneline', '-20']
        
        result = subprocess.run(cmd, capture_output=True, text=True, check=True)
        return result.stdout.strip().split('\n') if result.stdout.strip() else []
    except subprocess.CalledProcessError:
        return []


def categorize_commits(commits: list) -> dict:
    """Categorize commits by type."""
    categories = {
        'breaking': [],
        'feature': [],
        'bugfix': [],
        'docs': [],
        'style': [],
        'refactor': [],
        'test': [],
        'chore': [],
        'other': []
    }
    
    for commit in commits:
        if not commit:
            continue
        
        # Extract commit type from message
        if 'BREAKING CHANGE' in commit or commit.startswith('!'):
            categories['breaking'].append(commit)
        elif commit.startswith('feat'):
            categories['feature'].append(commit)
        elif commit.startswith('fix') or commit.startswith('bugfix'):
            categories['bugfix'].append(commit)
        elif commit.startswith('docs'):
            categories['docs'].append(commit)
        elif commit.startswith('style'):
            categories['style'].append(commit)
        elif commit.startswith('refactor'):
            categories['refactor'].append(commit)
        elif commit.startswith('test'):
            categories['test'].append(commit)
        elif commit.startswith('chore'):
            categories['chore'].append(commit)
        else:
            categories['other'].append(commit)
    
    return categories


def generate_changelog(current_version: str, new_version: str, commits: list) -> str:
    """Generate changelog entry."""
    categories = categorize_commits(commits)
    
    changelog = f"## [{new_version}] - {datetime.now().strftime('%Y-%m-%d')}\n\n"
    
    if categories['breaking']:
        changelog += "### ⚠️ BREAKING CHANGES\n"
        for commit in categories['breaking']:
            changelog += f"- {commit}\n"
        changelog += "\n"
    
    if categories['feature']:
        changelog += "### ✨ Features\n"
        for commit in categories['feature']:
            changelog += f"- {commit}\n"
        changelog += "\n"
    
    if categories['bugfix']:
        changelog += "### 🐛 Bug Fixes\n"
        for commit in categories['bugfix']:
            changelog += f"- {commit}\n"
        changelog += "\n"
    
    if categories['docs']:
        changelog += "### 📚 Documentation\n"
        for commit in categories['docs']:
            changelog += f"- {commit}\n"
        changelog += "\n"
    
    if categories['refactor']:
        changelog += "### ♻️ Refactoring\n"
        for commit in categories['refactor']:
            changelog += f"- {commit}\n"
        changelog += "\n"
    
    if categories['other']:
        changelog += "### 🔧 Other Changes\n"
        for commit in categories['other']:
            changelog += f"- {commit}\n"
        changelog += "\n"
    
    return changelog


def create_git_tag(version: str, message: str = "") -> None:
    """Create annotated git tag."""
    tag_name = f"v{version}"
    tag_message = message or f"Release {version}"
    
    try:
        subprocess.run(
            ['git', 'tag', '-a', tag_name, '-m', tag_message],
            check=True,
            capture_output=True
        )
        print(f"✅ Created tag: {tag_name}")
    except subprocess.CalledProcessError as e:
        print(f"❌ Failed to create tag: {e}")
        sys.exit(1)


def show_version(pyproject_path: Path) -> None:
    """Show current version."""
    version = get_current_version(pyproject_path)
    print(f"Current version: {version}")


def bump_version(pyproject_path: Path, bump_type: str, tag: bool = False) -> None:
    """Bump version and optionally create tag."""
    current = get_current_version(pyproject_path)
    sem_version = SemanticVersion(current)
    
    # Bump version
    if bump_type == 'major':
        new_version = sem_version.bump_major()
    elif bump_type == 'minor':
        new_version = sem_version.bump_minor()
    elif bump_type == 'patch':
        new_version = sem_version.bump_patch()
    else:
        print(f"❌ Unknown bump type: {bump_type}")
        sys.exit(1)
    
    new_version_str = str(new_version)
    
    # Update files
    update_version_in_file(pyproject_path, new_version_str)
    
    chronos_init = pyproject_path.parent / 'chronos' / '__init__.py'
    if chronos_init.exists():
        update_version_in_file(chronos_init, new_version_str)
    
    print(f"✅ Bumped {bump_type}: {current} → {new_version_str}")
    
    if tag:
        create_git_tag(new_version_str, f"Release {new_version_str}")


def create_prerelease(pyproject_path: Path, prerelease_type: str, tag: bool = False) -> None:
    """Create prerelease version."""
    current = get_current_version(pyproject_path)
    sem_version = SemanticVersion(current)
    
    # Create prerelease
    new_version = sem_version.create_prerelease(prerelease_type)
    new_version_str = str(new_version)
    
    # Update files
    update_version_in_file(pyproject_path, new_version_str)
    
    chronos_init = pyproject_path.parent / 'chronos' / '__init__.py'
    if chronos_init.exists():
        update_version_in_file(chronos_init, new_version_str)
    
    print(f"✅ Created prerelease: {current} → {new_version_str}")
    
    if tag:
        create_git_tag(new_version_str, f"Prerelease {new_version_str}")


def main():
    """Main entry point."""
    parser = argparse.ArgumentParser(
        description='Semantic Versioning Manager for CHRONOS'
    )
    
    parser.add_argument(
        'command',
        choices=['show', 'major', 'minor', 'patch', 'prerelease', 'finalize', 'changelog', 'tag'],
        help='Version command to execute'
    )
    
    parser.add_argument(
        '--tag',
        action='store_true',
        help='Create git tag after bumping version'
    )
    
    parser.add_argument(
        '--prerelease-type',
        choices=['alpha', 'beta', 'rc'],
        default='alpha',
        help='Type of prerelease'
    )
    
    parser.add_argument(
        '--pyproject',
        type=Path,
        default=Path('pyproject.toml'),
        help='Path to pyproject.toml'
    )
    
    args = parser.parse_args()
    
    if not args.pyproject.exists():
        print(f"❌ File not found: {args.pyproject}")
        sys.exit(1)
    
    try:
        if args.command == 'show':
            show_version(args.pyproject)
        
        elif args.command == 'major':
            bump_version(args.pyproject, 'major', tag=args.tag)
        
        elif args.command == 'minor':
            bump_version(args.pyproject, 'minor', tag=args.tag)
        
        elif args.command == 'patch':
            bump_version(args.pyproject, 'patch', tag=args.tag)
        
        elif args.command == 'prerelease':
            create_prerelease(args.pyproject, args.prerelease_type, tag=args.tag)
        
        elif args.command == 'changelog':
            current = get_current_version(args.pyproject)
            commits = get_git_commits()
            changelog = generate_changelog(current, current, commits)
            print(changelog)
        
        elif args.command == 'tag':
            version = get_current_version(args.pyproject)
            create_git_tag(version)
    
    except Exception as e:
        print(f"❌ Error: {e}")
        sys.exit(1)


if __name__ == '__main__':
    main()
