"""
Version Manager for S3-backed reference data versioning.

Handles S3 version operations following Single Responsibility Principle.
Simplified for local-first architecture with automatic S3 versioning.
"""

import json
from datetime import datetime
from pathlib import Path
from typing import List, Optional
import boto3
from botocore.exceptions import ClientError, NoCredentialsError

from src.log_context import get_logger
from .models import VersionInfo, ManifestInfo


class VersionManager:
    """
    Manages S3 version operations for reference data.

    Single responsibility: S3 version lifecycle management.
    """

    def __init__(self, s3_service):
        """Initialize with S3 service dependency."""
        self.logger = get_logger(__name__)
        self.s3_service = s3_service
        self.s3_client = boto3.client('s3')

    def create_version_backup(self, operation: str) -> Optional[str]:
        """
        Create timestamped version backup of current S3 data if it exists.

        Args:
            operation: Description of operation that triggered backup

        Returns:
            Version ID (timestamp) of created backup, or None if no current data exists

        Raises:
            RuntimeError: If backup creation fails when current data exists
        """
        try:
            # Check if current data exists in S3
            bucket = self.s3_service.get_config_value("reference_data_bucket")
            current_key = f"{self.s3_service.get_config_value('reference_data_prefix')}/current.parquet"

            try:
                self.s3_client.head_object(Bucket=bucket, Key=current_key)
            except ClientError as e:
                if e.response['Error']['Code'] == '404':
                    self.logger.info(f"No current S3 data exists - skipping backup for {operation}")
                    return None
                else:
                    raise

            version_id = self._generate_version_id()

            # Download current data to local temp location
            current_path = self.s3_service.get_local_cache_path()
            temp_backup_path = current_path.parent / f"backup_{version_id}.parquet"

            self.s3_service.download_reference_data_to_path(temp_backup_path)

            # Upload as versioned backup
            version_key = f"{self.s3_service.get_config_value('reference_data_prefix')}/versions/{version_id}.parquet"

            self.s3_client.upload_file(
                str(temp_backup_path),
                bucket,
                version_key
            )

            # Clean up temp file
            temp_backup_path.unlink()

            self.logger.info(f"Created version backup {version_id} for operation: {operation}")
            return version_id

        except (ClientError, NoCredentialsError) as e:
            error_msg = f"Failed to create version backup: {e}"
            self.logger.error(error_msg)
            raise RuntimeError(error_msg) from e

    def list_versions(self) -> List[VersionInfo]:
        """
        List all available versions in S3.

        Returns:
            List of VersionInfo objects sorted by creation time (newest first)
        """
        try:
            bucket = self.s3_service.get_config_value("reference_data_bucket")
            versions_prefix = f"{self.s3_service.get_config_value('reference_data_prefix')}/versions/"

            response = self.s3_client.list_objects_v2(
                Bucket=bucket,
                Prefix=versions_prefix
            )

            versions = []
            if 'Contents' in response:
                for obj in response['Contents']:
                    version_id = self._extract_version_id_from_key(obj['Key'])
                    if version_id:
                        versions.append(VersionInfo(
                            version_id=version_id,
                            created_at=obj['LastModified'],
                            file_size_mb=obj['Size'] / (1024 * 1024),
                            operation="backup",  # Default, could be enhanced later
                            checksum=obj.get('ETag', '').strip('"'),
                            is_current=False  # Will be set by caller if needed
                        ))

            # Sort by creation time (newest first)
            versions.sort(key=lambda v: v.created_at, reverse=True)
            return versions

        except (ClientError, NoCredentialsError) as e:
            self.logger.error(f"Failed to list versions: {e}")
            return []

    def restore_version(self, version_id: str) -> bool:
        """
        Restore a specific version to current and local cache.

        Args:
            version_id: Version ID to restore

        Returns:
            True if restore successful, False otherwise
        """
        try:
            # First create backup of current state
            backup_id = self.create_version_backup("restore-backup")
            self.logger.info(f"Created restore backup: {backup_id}")

            # Download version to restore
            bucket = self.s3_service.get_config_value("reference_data_bucket")
            version_key = f"{self.s3_service.get_config_value('reference_data_prefix')}/versions/{version_id}.parquet"
            current_key = f"{self.s3_service.get_config_value('reference_data_prefix')}/current.parquet"

            # Copy version to current
            self.s3_client.copy_object(
                CopySource={'Bucket': bucket, 'Key': version_key},
                Bucket=bucket,
                Key=current_key
            )

            # Download to local cache
            local_path = self.s3_service.get_local_cache_path()
            self.s3_service.download_reference_data_to_path(local_path)

            self.logger.info(f"Restored version {version_id} to current and local cache")
            return True

        except (ClientError, NoCredentialsError) as e:
            self.logger.error(f"Failed to restore version {version_id}: {e}")
            return False

    def get_manifest(self) -> Optional[ManifestInfo]:
        """
        Get current S3 manifest information.

        Returns:
            ManifestInfo if available, None otherwise
        """
        try:
            bucket = self.s3_service.get_config_value("reference_data_bucket")
            manifest_key = f"{self.s3_service.get_config_value('reference_data_prefix')}/manifest.json"

            response = self.s3_client.get_object(Bucket=bucket, Key=manifest_key)
            manifest_data = json.loads(response['Body'].read().decode('utf-8'))

            return ManifestInfo(
                current_version=manifest_data['current_version'],
                total_versions=manifest_data['total_versions'],
                last_sync=datetime.fromisoformat(manifest_data['last_sync']),
                sync_checksum=manifest_data['sync_checksum'],
                last_operation=manifest_data['last_operation'],
                created_by=manifest_data.get('created_by', 'system')
            )

        except (ClientError, NoCredentialsError) as e:
            if e.response.get('Error', {}).get('Code') == 'NoSuchKey':
                self.logger.debug("No manifest exists yet")
                return None
            else:
                self.logger.error(f"Failed to get manifest: {e}")
                return None

    def update_manifest(self, version_id: str, operation: str, checksum: str):
        """
        Update S3 manifest with current state.

        Args:
            version_id: Current version ID
            operation: Operation that caused this update
            checksum: Current data checksum
        """
        try:
            # Count total versions
            versions = self.list_versions()
            total_versions = len(versions)

            manifest_data = {
                "current_version": version_id,
                "total_versions": total_versions,
                "last_sync": datetime.utcnow().isoformat(),
                "sync_checksum": checksum,
                "last_operation": operation,
                "created_by": "czbg-version-system"
            }

            # Upload manifest
            bucket = self.s3_service.get_config_value("reference_data_bucket")
            manifest_key = f"{self.s3_service.get_config_value('reference_data_prefix')}/manifest.json"

            self.s3_client.put_object(
                Bucket=bucket,
                Key=manifest_key,
                Body=json.dumps(manifest_data, indent=2),
                ContentType='application/json'
            )

            self.logger.debug(f"Updated manifest with version {version_id}")

        except (ClientError, NoCredentialsError) as e:
            self.logger.error(f"Failed to update manifest: {e}")

    def cleanup_old_versions(self, keep_count: int = 10) -> int:
        """
        Clean up old versions, keeping only the most recent.

        Args:
            keep_count: Number of versions to keep

        Returns:
            Number of versions deleted
        """
        try:
            versions = self.list_versions()

            if len(versions) <= keep_count:
                self.logger.info(f"Only {len(versions)} versions exist, no cleanup needed")
                return 0

            versions_to_delete = versions[keep_count:]
            bucket = self.s3_service.get_config_value("reference_data_bucket")
            prefix = self.s3_service.get_config_value('reference_data_prefix')

            deleted_count = 0
            for version in versions_to_delete:
                try:
                    version_key = f"{prefix}/versions/{version.version_id}.parquet"
                    self.s3_client.delete_object(Bucket=bucket, Key=version_key)
                    deleted_count += 1
                    self.logger.debug(f"Deleted version {version.version_id}")
                except ClientError as e:
                    self.logger.warning(f"Failed to delete version {version.version_id}: {e}")

            self.logger.info(f"Cleaned up {deleted_count} old versions, kept {keep_count} most recent")
            return deleted_count

        except (ClientError, NoCredentialsError) as e:
            self.logger.error(f"Failed to cleanup versions: {e}")
            return 0

    def _generate_version_id(self) -> str:
        """Generate timestamp-based version ID."""
        return datetime.utcnow().strftime("%Y-%m-%dT%H-%M-%SZ")

    def _extract_version_id_from_key(self, s3_key: str) -> Optional[str]:
        """Extract version ID from S3 key path."""
        try:
            # Extract filename without extension
            filename = Path(s3_key).stem
            # Validate it looks like a timestamp
            if len(filename) == 20 and filename.endswith('Z'):
                return filename
            return None
        except Exception:
            return None