"""Release lifecycle management for Debian Repository Manager.

This module provides functionality for adding and removing distribution releases,
including repository creation, backup management, and activity tracking.
"""

import logging
import os
import subprocess
import tarfile
from pathlib import Path
from typing import Dict, List, Optional

from debrepomanager.aptly import AptlyManager
from debrepomanager.config import Config

logger = logging.getLogger(__name__)


class ReleaseError(Exception):
    """Exception raised for release management errors."""

    pass


class ReleaseManager:
    """Manager for distribution release lifecycle operations."""

    def __init__(self, config: Config, aptly: AptlyManager):
        """Initialize ReleaseManager.

        Args:
            config: Configuration instance
            aptly: AptlyManager instance
        """
        self.config = config
        self.aptly = aptly

    def add_release(
        self,
        codename: str,
        components: Optional[List[str]] = None,
        skip_symlinks: bool = False,
    ) -> Dict:
        """Add new distribution release.

        Creates repositories for all specified components and optionally
        creates dual format symlinks for backward compatibility.

        Args:
            codename: Distribution codename (e.g., trixie, oracular)
            components: List of components to create
                (default: jethome-tools, jethome-{codename}, jethome-armbian)
            skip_symlinks: Skip dual format symlink creation

        Returns:
            Dict with operation results:
            - created: number of repositories created
            - failed: number of failed operations
            - components: list of created component names
            - failures: list of failed operations with errors

        Raises:
            ReleaseError: If release addition fails
        """
        # Default components if not specified
        if components is None:
            components = [
                "jethome-tools",
                f"jethome-{codename}",
                "jethome-armbian",
            ]

        logger.info(f"Adding release {codename} with components: {components}")

        created_count = 0
        failed_count = 0
        created_components = []
        failures = []

        # Create repositories for each component
        for component in components:
            try:
                logger.info(f"Creating repository: {codename}/{component}")
                self.aptly.create_repo(codename, component)
                created_count += 1
                created_components.append(component)
                logger.info(f"✓ Created {codename}/{component}")
            except Exception as e:
                failed_count += 1
                error_msg = str(e)
                failures.append({"component": component, "error": error_msg})
                logger.error(f"✗ Failed to create {codename}/{component}: {error_msg}")

        # Create dual format symlinks if not skipped
        if not skip_symlinks and created_count > 0:
            try:
                logger.info("Creating dual format symlinks...")
                self._fix_symlinks(codename)
                logger.info("✓ Symlinks created")
            except Exception as e:
                logger.warning(f"⚠️  Failed to create symlinks: {e}")
                # Non-critical, don't fail the operation

        result = {
            "codename": codename,
            "created": created_count,
            "failed": failed_count,
            "components": created_components,
            "failures": failures,
        }

        if failed_count > 0:
            logger.warning(f"Release {codename} added with {failed_count} failures")
        else:
            logger.info(f"✓ Release {codename} added successfully")

        return result

    def remove_release(  # noqa: C901
        self,
        codename: str,
        backup: bool = True,
        force: bool = False,
        backup_dir: str = "/backup",
    ) -> Dict:
        """Remove old distribution release.

        Removes all repositories for specified codename, optionally creating
        backup before removal. Can check activity in nginx logs before removal.

        Args:
            codename: Distribution codename to remove
            backup: Create backup before removal
            force: Skip activity check
            backup_dir: Directory for backups

        Returns:
            Dict with operation results:
            - deleted: number of repositories deleted
            - failed: number of failed deletions
            - backup_file: path to backup file (if created)
            - activity: activity count from logs (if checked)

        Raises:
            ReleaseError: If removal fails or validation fails
        """
        logger.info(f"Removing release {codename}")

        # Check activity if not forced
        activity_count = 0
        if not force:
            try:
                activity_count = self.check_activity(codename)
                logger.info(f"Activity in logs: {activity_count} requests")

                if activity_count > 100:
                    logger.warning(
                        f"⚠️  WARNING: Codename {codename} has "
                        f"{activity_count} requests in logs"
                    )
                    # Note: Interactive confirmation should be handled at CLI level
            except Exception as e:
                logger.warning(f"⚠️  Could not check activity: {e}")

        # Create backup if requested
        backup_file = None
        if backup:
            try:
                backup_file = self.create_backup(codename, backup_dir)
                logger.info(f"✓ Backup created: {backup_file}")
            except Exception as e:
                logger.error(f"✗ Backup failed: {e}")
                raise ReleaseError(f"Backup failed: {e}")

        # Get list of repositories for this codename
        try:
            repos = self.aptly.list_repos(codename)
        except Exception as e:
            logger.error(f"Failed to list repositories: {e}")
            raise ReleaseError(f"Failed to list repositories: {e}")

        deleted_count = 0
        failed_count = 0
        failures = []

        # Delete each repository
        for repo_name in repos:
            # Extract component from repo name (format: {component}-{codename})
            if repo_name.endswith(f"-{codename}"):
                component = repo_name[: -len(codename) - 1]
            else:
                logger.warning(f"Unexpected repo name format: {repo_name}")
                component = repo_name

            try:
                logger.info(f"Deleting {codename}/{component}...")
                self.aptly.delete_repo(codename, component)
                deleted_count += 1
                logger.info(f"✓ Deleted {codename}/{component}")
            except Exception as e:
                failed_count += 1
                error_msg = str(e)
                failures.append({"component": component, "error": error_msg})
                logger.error(f"✗ Failed to delete {codename}/{component}: {error_msg}")

        # Clean up files manually (in case aptly didn't remove everything)
        self._cleanup_files(codename)

        result = {
            "codename": codename,
            "deleted": deleted_count,
            "failed": failed_count,
            "backup_file": backup_file,
            "activity": activity_count,
            "failures": failures,
        }

        if failed_count > 0:
            logger.warning(f"Release {codename} removed with {failed_count} failures")
        else:
            logger.info(f"✓ Release {codename} removed successfully")

        return result

    def check_activity(
        self,
        codename: str,
        log_paths: Optional[List[str]] = None,
    ) -> int:
        """Check codename activity in nginx logs.

        Args:
            codename: Distribution codename
            log_paths: List of log file paths (default: standard nginx paths)

        Returns:
            Number of requests found in logs

        Raises:
            ReleaseError: If log check fails
        """
        if log_paths is None:
            log_paths = [
                "/var/log/nginx/repo-access.log",
                "/var/log/nginx/repo-access.log.1",
            ]

        total_count = 0

        for log_path in log_paths:
            if not os.path.exists(log_path):
                logger.debug(f"Log file not found: {log_path}")
                continue

            try:
                # Use grep to search for codename in logs
                result = subprocess.run(
                    ["grep", f"/{codename}/", log_path],
                    capture_output=True,
                    text=True,
                    check=False,
                )

                # Count lines (grep returns 0 if found, 1 if not found)
                if result.returncode == 0:
                    count = len(result.stdout.strip().split("\n"))
                    total_count += count
                    logger.debug(f"Found {count} requests in {log_path}")

            except Exception as e:
                logger.warning(f"Failed to check log {log_path}: {e}")
                continue

        return total_count

    def create_backup(
        self,
        codename: str,
        backup_dir: str = "/backup",
    ) -> str:
        """Create tar.gz backup of codename.

        Args:
            codename: Distribution codename
            backup_dir: Directory to store backup

        Returns:
            Path to created backup file

        Raises:
            ReleaseError: If backup creation fails
        """
        from datetime import datetime

        # Create backup directory if doesn't exist
        os.makedirs(backup_dir, exist_ok=True)

        # Generate backup filename with timestamp
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_filename = f"repo-{codename}-{timestamp}.tar.gz"
        backup_path = os.path.join(backup_dir, backup_filename)

        logger.info(f"Creating backup: {backup_path}")

        # Paths to backup
        paths_to_backup = []

        # Aptly root (support multiple locations)
        for root_base in ["/srv/aptly", "/opt/repo"]:
            aptly_path = os.path.join(root_base, codename)
            if os.path.exists(aptly_path):
                paths_to_backup.append(aptly_path)

        # Published files
        publish_base = Path(self.config.publish_base)
        codename_publish = publish_base / codename
        if codename_publish.exists():
            paths_to_backup.append(str(codename_publish))

        # Dual format symlinks
        symlink_path = publish_base / "dists" / codename
        if symlink_path.exists():
            paths_to_backup.append(str(symlink_path))

        if not paths_to_backup:
            logger.warning(f"No files found to backup for {codename}")
            # Create empty marker file
            Path(backup_path).touch()
            return backup_path

        # Create tar.gz archive
        try:
            with tarfile.open(backup_path, "w:gz") as tar:
                for path in paths_to_backup:
                    if os.path.exists(path):
                        tar.add(path, arcname=os.path.basename(path))
                        logger.debug(f"Added to backup: {path}")

            # Get backup size
            backup_size = os.path.getsize(backup_path)
            logger.info(f"Backup created: {backup_path} ({backup_size} bytes)")

            return backup_path

        except Exception as e:
            logger.error(f"Failed to create backup: {e}")
            raise ReleaseError(f"Failed to create backup: {e}")

    def _fix_symlinks(self, codename: str) -> None:
        """Create dual format symlinks for codename.

        Args:
            codename: Distribution codename

        Raises:
            ReleaseError: If symlink creation fails
        """
        # This is similar to fix-symlinks command but focused on one codename
        # We'll call the existing logic from aptly manager if available
        # For now, we'll do a basic implementation

        publish_base = Path(self.config.publish_base)
        codename_dir = publish_base / codename

        if not codename_dir.exists():
            logger.warning(f"Publish directory doesn't exist: {codename_dir}")
            return

        # Look for published components
        dists_dir = codename_dir / "dists"
        if not dists_dir.exists():
            logger.debug(f"No dists directory found: {dists_dir}")
            return

        for comp_dir in dists_dir.iterdir():
            if not comp_dir.is_dir():
                continue

            component = comp_dir.name

            # Create symlink:
            # /dists/{codename}/{component} -> ../../{codename}/dists/{component}
            old_format_dir = publish_base / "dists" / codename
            old_format_dir.mkdir(parents=True, exist_ok=True)

            symlink_path = old_format_dir / component
            target_path = codename_dir / "dists" / component

            if symlink_path.exists():
                if symlink_path.is_symlink():
                    # Check if correct
                    existing_target = os.readlink(symlink_path)
                    expected_target = os.path.relpath(target_path, symlink_path.parent)
                    if existing_target == expected_target:
                        logger.debug(f"Symlink OK: {symlink_path}")
                        continue
                    else:
                        # Remove incorrect symlink
                        symlink_path.unlink()
                else:
                    logger.warning(f"Path exists but not symlink: {symlink_path}")
                    continue

            # Create symlink
            rel_target = os.path.relpath(target_path, symlink_path.parent)
            os.symlink(rel_target, symlink_path)
            logger.debug(f"Created symlink: {symlink_path} -> {rel_target}")

    def _cleanup_files(self, codename: str) -> None:  # noqa: C901
        """Clean up files for removed codename.

        Args:
            codename: Distribution codename
        """
        import shutil

        publish_base = Path(self.config.publish_base)

        # List of paths to clean
        paths_to_remove = []

        # Aptly roots (support different paths)
        for root_base in ["/srv/aptly", "/opt/repo"]:
            aptly_path = Path(root_base) / codename
            if aptly_path.exists():
                paths_to_remove.append(aptly_path)

        # Published files
        codename_publish = publish_base / codename
        if codename_publish.exists():
            paths_to_remove.append(codename_publish)

        # Dual format symlinks
        symlink_path = publish_base / "dists" / codename
        if symlink_path.exists():
            paths_to_remove.append(symlink_path)

        # Remove paths
        for path in paths_to_remove:
            try:
                if path.is_symlink() or path.is_file():
                    path.unlink()
                    logger.info(f"✓ Removed {path}")
                elif path.is_dir():
                    shutil.rmtree(path)
                    logger.info(f"✓ Removed {path}/")
            except PermissionError:
                # Try with sudo (if available)
                try:
                    subprocess.run(
                        ["sudo", "rm", "-rf", str(path)],
                        check=True,
                        capture_output=True,
                    )
                    logger.info(f"✓ Removed {path} (with sudo)")
                except subprocess.CalledProcessError as e:
                    logger.warning(f"⚠️  Failed to remove {path}: {e}")
            except Exception as e:
                logger.warning(f"⚠️  Failed to remove {path}: {e}")
