"""Retention policy enforcement for backups."""

from __future__ import annotations

import logging
from datetime import datetime, timedelta
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from gaol.backup.models import BackupMetadata, RetentionPolicy
    from gaol.backup.storage.base import StorageBackend
    from gaol.backup.store import BackupStore


logger = logging.getLogger(__name__)


class RetentionEnforcer:
    """Apply retention policies to backups.

    Implements retention rules:
    - keep_last: Keep the N most recent backups
    - keep_daily: Keep one backup per day for N days
    - keep_weekly: Keep one backup per week for N weeks
    - keep_monthly: Keep one backup per month for N months
    - max_age_days: Delete backups older than N days
    - max_total_size_mb: Keep total size under N MB

    Example:
        >>> enforcer = RetentionEnforcer(store, storage)
        >>> deleted = enforcer.apply_retention("mycontainer", policy)
    """

    def __init__(self, store: BackupStore, storage: StorageBackend) -> None:
        """Initialize the retention enforcer.

        Args:
            store: Backup store for metadata
            storage: Storage backend for deleting files
        """
        self._store = store
        self._storage = storage

    def apply_retention(
        self,
        container_name: str,
        policy: RetentionPolicy,
        dry_run: bool = False,
    ) -> list[BackupMetadata]:
        """Apply retention policy to a container's backups.

        Args:
            container_name: Container name
            policy: Retention policy to apply
            dry_run: If True, don't actually delete anything

        Returns:
            List of backups that were (or would be) deleted
        """
        backups = self._store.list_backups(container_name)
        if not backups:
            return []

        # Sort by creation date (newest first)
        backups.sort(key=lambda b: b.created_at, reverse=True)

        to_keep: set[str] = set()
        to_delete: list[BackupMetadata] = []

        # Apply keep_last
        if policy.keep_last:
            for backup in backups[: policy.keep_last]:
                to_keep.add(backup.backup_id)

        # Apply keep_daily (one per day for N days)
        if policy.keep_daily:
            daily_kept = 0
            seen_days: set[str] = set()
            for backup in backups:
                day_key = backup.created_at.strftime("%Y-%m-%d")
                if day_key not in seen_days and daily_kept < policy.keep_daily:
                    to_keep.add(backup.backup_id)
                    seen_days.add(day_key)
                    daily_kept += 1

        # Apply keep_weekly (one per week for N weeks)
        if policy.keep_weekly:
            weekly_kept = 0
            seen_weeks: set[str] = set()
            for backup in backups:
                week_key = backup.created_at.strftime("%Y-W%W")
                if week_key not in seen_weeks and weekly_kept < policy.keep_weekly:
                    to_keep.add(backup.backup_id)
                    seen_weeks.add(week_key)
                    weekly_kept += 1

        # Apply keep_monthly (one per month for N months)
        if policy.keep_monthly:
            monthly_kept = 0
            seen_months: set[str] = set()
            for backup in backups:
                month_key = backup.created_at.strftime("%Y-%m")
                if month_key not in seen_months and monthly_kept < policy.keep_monthly:
                    to_keep.add(backup.backup_id)
                    seen_months.add(month_key)
                    monthly_kept += 1

        # Apply max_age_days - remove old backups from keep set
        if policy.max_age_days:
            cutoff = datetime.now() - timedelta(days=policy.max_age_days)
            for backup in backups:
                if backup.created_at < cutoff:
                    to_keep.discard(backup.backup_id)

        # Collect backups to delete (not in keep set)
        for backup in backups:
            if backup.backup_id not in to_keep:
                to_delete.append(backup)

        # Apply max_total_size_mb - remove oldest backups until under limit
        if policy.max_total_size_mb:
            max_bytes = policy.max_total_size_mb * 1024 * 1024

            # Calculate current total of kept backups
            remaining = [b for b in backups if b.backup_id in to_keep]
            remaining.sort(key=lambda b: b.created_at, reverse=True)

            total_size = sum(b.size_bytes for b in remaining)

            # Remove oldest until under limit
            while total_size > max_bytes and remaining:
                oldest = remaining.pop()
                to_keep.discard(oldest.backup_id)
                to_delete.append(oldest)
                total_size -= oldest.size_bytes

        # Deduplicate to_delete
        seen_ids = set()
        unique_delete = []
        for backup in to_delete:
            if backup.backup_id not in seen_ids:
                seen_ids.add(backup.backup_id)
                unique_delete.append(backup)
        to_delete = unique_delete

        # Actually delete if not dry run
        if not dry_run:
            for backup in to_delete:
                self._delete_backup(container_name, backup)

        return to_delete

    def apply_retention_all(
        self,
        dry_run: bool = False,
    ) -> dict[str, list[BackupMetadata]]:
        """Apply retention policies to all containers with policies.

        Args:
            dry_run: If True, don't actually delete anything

        Returns:
            Dictionary mapping container names to deleted backups
        """
        results: dict[str, list[BackupMetadata]] = {}

        policies = self._store.list_policies()
        for policy in policies:
            deleted = self.apply_retention(
                policy.container_name,
                policy.retention,
                dry_run=dry_run,
            )
            if deleted:
                results[policy.container_name] = deleted

        return results

    def _delete_backup(
        self, container_name: str, backup: BackupMetadata
    ) -> None:
        """Delete a backup from storage and metadata.

        Args:
            container_name: Container name
            backup: Backup metadata
        """
        try:
            # Delete from storage
            self._storage.delete(backup.storage_path)
        except Exception as e:
            logger.warning(f"Failed to delete backup file: {e}")

        # Delete metadata
        self._store.delete_backup_metadata(container_name, backup.backup_id)

        # Update history
        history = self._store.get_history(container_name)
        history.remove_backup(backup.backup_id)
        self._store.save_history(history)

        logger.info(f"Deleted backup {backup.backup_name} for {container_name}")

    def get_retention_preview(
        self,
        container_name: str,
        policy: RetentionPolicy,
    ) -> tuple[list[BackupMetadata], list[BackupMetadata]]:
        """Preview what would be kept and deleted by retention policy.

        Args:
            container_name: Container name
            policy: Retention policy to preview

        Returns:
            Tuple of (backups to keep, backups to delete)
        """
        to_delete = self.apply_retention(container_name, policy, dry_run=True)
        delete_ids = {b.backup_id for b in to_delete}

        all_backups = self._store.list_backups(container_name)
        to_keep = [b for b in all_backups if b.backup_id not in delete_ids]

        return to_keep, to_delete
