name: Cleanup Old Docker Images

on:
  # Run after successful releases
  workflow_run:
    workflows: ["Main CI/CD Pipeline", "Docker Publish (Tags)", "Publish and Test (Tags)"]
    types:
      - completed
  
  # Run weekly on Sunday at 2 AM UTC
  schedule:
    - cron: '0 2 * * 0'
  
  # Allow manual trigger with options
  workflow_dispatch:
    inputs:
      dry_run:
        description: 'Dry run (no deletions)'
        required: false
        type: boolean
        default: true
      keep_versions:
        description: 'Number of versions to keep'
        required: false
        type: string
        default: '5'
      delete_untagged:
        description: 'Delete untagged images'
        required: false
        type: boolean
        default: true

env:
  DOCKER_IMAGE: doobidoo/mcp-memory-service
  GHCR_IMAGE: ghcr.io/doobidoo/mcp-memory-service

jobs:
  # Cleanup GitHub Container Registry images
  cleanup-ghcr:
    runs-on: ubuntu-latest
    name: Cleanup GHCR Images
    permissions:
      packages: write
      contents: read
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
    
    - name: Delete untagged images from GHCR
      if: github.event.inputs.delete_untagged != 'false'
      uses: actions/delete-package-versions@v4
      continue-on-error: true
      with:
        package-name: 'mcp-memory-service'
        package-type: 'container'
        token: ${{ secrets.GITHUB_TOKEN }}
        min-versions-to-keep: 0
        delete-only-untagged-versions: 'true'
    
    - name: Clean old GHCR versions (keep last N)
      uses: actions/delete-package-versions@v4
      continue-on-error: true
      with:
        package-name: 'mcp-memory-service'
        package-type: 'container'
        token: ${{ secrets.GITHUB_TOKEN }}
        min-versions-to-keep: ${{ github.event.inputs.keep_versions || '5' }}
        ignore-versions: '^(latest|slim|main|v[0-9]+\.[0-9]+\.[0-9]+)$'
        delete-only-pre-release-versions: 'false'
    
    - name: Clean buildcache tags older than 7 days
      uses: snok/container-retention-policy@v1
      continue-on-error: true
      with:
        image-names: mcp-memory-service
        cut-off: 7 days ago UTC
        account-type: personal
        token: ${{ secrets.GITHUB_TOKEN }}
        filter-tags: 'buildcache-*'
        filter-include-untagged: false
        skip-tags: latest,slim,main
        dry-run: ${{ github.event.inputs.dry_run || 'false' }}

  # Cleanup Docker Hub images
  cleanup-dockerhub:
    runs-on: ubuntu-latest
    name: Cleanup Docker Hub Images
    if: github.event_name != 'pull_request'
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
    
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'
        cache: 'pip'
        cache-dependency-path: '**/pyproject.toml'
    
    - name: Install dependencies
      run: |
        pip install requests python-dateutil
    
    - name: Validate Docker Hub credentials
      run: |
        if [ -z "${{ secrets.DOCKER_USERNAME }}" ] || [ -z "${{ secrets.DOCKER_PASSWORD }}" ]; then
          echo "⚠️  Docker Hub credentials not available, skipping Docker Hub cleanup"
          echo "SKIP_DOCKER_CLEANUP=true" >> $GITHUB_ENV
        else
          echo "✓ Docker Hub credentials available"
          echo "SKIP_DOCKER_CLEANUP=false" >> $GITHUB_ENV
        fi
    
    - name: Create cleanup script
      run: |
        cat > cleanup_docker_hub.py << 'EOF'
        #!/usr/bin/env python3
        """
        Docker Hub image cleanup script
        Deletes old image tags based on retention policy
        """
        import os
        import sys
        import json
        import requests
        from datetime import datetime, timedelta, timezone
        from dateutil import parser
        import re
        
        class DockerHubCleaner:
            def __init__(self, username, password, repository):
                self.username = username
                self.password = password
                self.repository = repository
                self.token = None
                self.api_base = "https://hub.docker.com/v2"
                
            def authenticate(self):
                """Get authentication token from Docker Hub"""
                url = f"{self.api_base}/users/login/"
                data = {"username": self.username, "password": self.password}
                
                try:
                    response = requests.post(url, json=data)
                    response.raise_for_status()
                    self.token = response.json()["token"]
                    print("✓ Authenticated with Docker Hub")
                    return True
                except Exception as e:
                    print(f"✗ Authentication failed: {e}")
                    return False
            
            def get_tags(self):
                """Get all tags for the repository"""
                url = f"{self.api_base}/repositories/{self.repository}/tags"
                headers = {"Authorization": f"Bearer {self.token}"}
                tags = []
                
                while url:
                    try:
                        response = requests.get(url, headers=headers, params={"page_size": 100})
                        response.raise_for_status()
                        data = response.json()
                        tags.extend(data["results"])
                        url = data.get("next")
                    except Exception as e:
                        print(f"✗ Failed to get tags: {e}")
                        break
                
                print(f"✓ Found {len(tags)} tags in repository")
                return tags
            
            def should_keep_tag(self, tag_name, tag_date, keep_versions, cutoff_date):
                """Determine if a tag should be kept based on retention policy"""
                # Always keep these tags
                protected_tags = ["latest", "slim", "main", "stable"]
                if tag_name in protected_tags:
                    return True, "Protected tag"
                
                # Keep semantic version tags (v1.2.3)
                if re.match(r'^v?\d+\.\d+\.\d+$', tag_name):
                    # Check if it's one of the most recent versions
                    return True, "Semantic version"
                
                # Keep major.minor tags (1.0, 2.1)
                if re.match(r'^v?\d+\.\d+$', tag_name):
                    return True, "Major.minor version"
                
                # Delete buildcache tags older than cutoff
                if tag_name.startswith("buildcache-"):
                    if tag_date < cutoff_date:
                        return False, "Old buildcache tag"
                    return True, "Recent buildcache tag"
                
                # Delete sha/digest tags older than cutoff
                if tag_name.startswith("sha256-") or len(tag_name) == 7:
                    if tag_date < cutoff_date:
                        return False, "Old sha/digest tag"
                    return True, "Recent sha/digest tag"
                
                # Delete test/dev tags older than cutoff
                if any(x in tag_name.lower() for x in ["test", "dev", "tmp", "temp"]):
                    if tag_date < cutoff_date:
                        return False, "Old test/dev tag"
                    return True, "Recent test/dev tag"
                
                # Keep if recent
                if tag_date >= cutoff_date:
                    return True, "Recent tag"
                
                return False, "Old tag"
            
            def delete_tag(self, tag_name, dry_run=False):
                """Delete a specific tag from Docker Hub"""
                if dry_run:
                    print(f"  [DRY RUN] Would delete: {tag_name}")
                    return True
                
                url = f"{self.api_base}/repositories/{self.repository}/tags/{tag_name}/"
                headers = {"Authorization": f"Bearer {self.token}"}
                
                try:
                    response = requests.delete(url, headers=headers)
                    response.raise_for_status()
                    print(f"  ✓ Deleted: {tag_name}")
                    return True
                except Exception as e:
                    print(f"  ✗ Failed to delete {tag_name}: {e}")
                    return False
            
            def cleanup(self, keep_versions=5, days_to_keep=30, dry_run=False):
                """Main cleanup function"""
                if not self.authenticate():
                    return False
                
                tags = self.get_tags()
                if not tags:
                    return False
                
                # Sort tags by date (newest first)
                tags.sort(key=lambda x: parser.parse(x["last_updated"]), reverse=True)
                
                # Calculate cutoff date
                cutoff_date = datetime.now(timezone.utc) - timedelta(days=days_to_keep)
                
                # Separate semantic versions from other tags
                version_tags = []
                other_tags = []
                
                for tag in tags:
                    if re.match(r'^v?\d+\.\d+\.\d+$', tag["name"]):
                        version_tags.append(tag)
                    else:
                        other_tags.append(tag)
                
                print(f"\n📊 Repository Statistics:")
                print(f"  - Total tags: {len(tags)}")
                print(f"  - Version tags: {len(version_tags)}")
                print(f"  - Other tags: {len(other_tags)}")
                print(f"  - Keep versions: {keep_versions}")
                print(f"  - Days to keep: {days_to_keep}")
                print(f"  - Dry run: {dry_run}")
                
                deleted_count = 0
                kept_count = 0
                
                print(f"\n🔍 Analyzing tags...")
                
                # Process version tags (keep only the most recent N)
                for i, tag in enumerate(version_tags):
                    tag_date = parser.parse(tag["last_updated"])
                    if i < keep_versions:
                        print(f"  ✓ Keep {tag['name']} (recent version #{i+1})")
                        kept_count += 1
                    else:
                        print(f"  ✗ Delete {tag['name']} (old version #{i+1})")
                        if self.delete_tag(tag["name"], dry_run):
                            deleted_count += 1
                
                # Process other tags based on policy
                for tag in other_tags:
                    tag_date = parser.parse(tag["last_updated"])
                    should_keep, reason = self.should_keep_tag(
                        tag["name"], tag_date, keep_versions, cutoff_date
                    )
                    
                    if should_keep:
                        print(f"  ✓ Keep {tag['name']} ({reason})")
                        kept_count += 1
                    else:
                        print(f"  ✗ Delete {tag['name']} ({reason})")
                        if self.delete_tag(tag["name"], dry_run):
                            deleted_count += 1
                
                print(f"\n📈 Summary:")
                print(f"  - Kept: {kept_count} tags")
                print(f"  - Deleted: {deleted_count} tags")
                
                return True
        
        def main():
            # Get environment variables
            username = os.environ.get("DOCKER_USERNAME")
            password = os.environ.get("DOCKER_PASSWORD")
            repository = os.environ.get("DOCKER_REPOSITORY", "doobidoo/mcp-memory-service")
            
            # Get parameters
            dry_run = os.environ.get("DRY_RUN", "false").lower() == "true"
            keep_versions = int(os.environ.get("KEEP_VERSIONS", "5"))
            days_to_keep = int(os.environ.get("DAYS_TO_KEEP", "30"))
            
            if not username or not password:
                print("✗ Missing DOCKER_USERNAME or DOCKER_PASSWORD")
                sys.exit(1)
            
            print(f"🐳 Docker Hub Cleanup for {repository}")
            print("=" * 50)
            
            cleaner = DockerHubCleaner(username, password, repository)
            success = cleaner.cleanup(keep_versions, days_to_keep, dry_run)
            
            sys.exit(0 if success else 1)
        
        if __name__ == "__main__":
            main()
        EOF
    
    - name: Run Docker Hub cleanup
      if: env.SKIP_DOCKER_CLEANUP != 'true'
      env:
        DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
        DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
        DOCKER_REPOSITORY: doobidoo/mcp-memory-service
        DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}
        KEEP_VERSIONS: ${{ github.event.inputs.keep_versions || '5' }}
        DAYS_TO_KEEP: '30'
      run: python cleanup_docker_hub.py
    
    - name: Docker Hub cleanup skipped
      if: env.SKIP_DOCKER_CLEANUP == 'true'
      run: echo "⚠️ Docker Hub cleanup was skipped due to missing credentials"

  # Report cleanup results
  report:
    needs: [cleanup-ghcr, cleanup-dockerhub]
    runs-on: ubuntu-latest
    if: always()
    name: Cleanup Report
    
    steps:
    - name: Generate cleanup report
      run: |
        echo "## 🧹 Docker Image Cleanup Report" >> $GITHUB_STEP_SUMMARY
        echo "" >> $GITHUB_STEP_SUMMARY
        echo "**Run Type**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
        echo "**Dry Run**: ${{ github.event.inputs.dry_run || 'false' }}" >> $GITHUB_STEP_SUMMARY
        echo "**Keep Versions**: ${{ github.event.inputs.keep_versions || '5' }}" >> $GITHUB_STEP_SUMMARY
        echo "" >> $GITHUB_STEP_SUMMARY
        echo "### Results" >> $GITHUB_STEP_SUMMARY
        echo "- GHCR Cleanup: ${{ needs.cleanup-ghcr.result }}" >> $GITHUB_STEP_SUMMARY
        echo "- Docker Hub Cleanup: ${{ needs.cleanup-dockerhub.result }}" >> $GITHUB_STEP_SUMMARY
        echo "" >> $GITHUB_STEP_SUMMARY
        echo "### Retention Policy" >> $GITHUB_STEP_SUMMARY
        echo "- **Protected tags**: latest, slim, main, stable" >> $GITHUB_STEP_SUMMARY
        echo "- **Version tags**: Keep last ${{ github.event.inputs.keep_versions || '5' }} versions" >> $GITHUB_STEP_SUMMARY
        echo "- **Build cache**: Delete after 7 days" >> $GITHUB_STEP_SUMMARY
        echo "- **Test/dev tags**: Delete after 30 days" >> $GITHUB_STEP_SUMMARY
        echo "- **Untagged images**: Delete immediately" >> $GITHUB_STEP_SUMMARY