"""
Manifest management service for append operations.

Handles manifest.json updates when both copied and newly uploaded files
exist in the same drop, ensuring proper manifest structure for CloudZero ingestion.
"""

from typing import Dict, List, Any, Optional
import json
import boto3
from botocore.exceptions import ClientError

from .manifest_reader import S3FileInfo
from ..models import UploadResult


class ManifestManager:
    """
    Service for managing manifest.json files during append operations.
    
    Single responsibility: Update manifests to reflect combined file sets
    from both copy and upload phases.
    """
    
    def __init__(self, s3_client, bucket: str, root_folder: str = "piedpiper-custom-billing"):
        """
        Initialize with S3 configuration.
        
        Args:
            s3_client: Boto3 S3 client
            bucket: S3 bucket name
            root_folder: Root folder for billing data
        """
        self.s3_client = s3_client
        self.bucket = bucket
        self.root_folder = root_folder
    
    def update_manifest_for_append_operation(
        self,
        organization: str,
        provider: str,
        billing_period: str,
        new_drop_id: str,
        copied_files: List[S3FileInfo],
        uploaded_files: List[UploadResult]
    ) -> Dict[str, Any]:
        """
        Update manifest.json to point to new drop containing both copied and uploaded files.
        
        Creates a new manifest that references the drop containing both
        the copied existing files and newly uploaded files.
        
        Args:
            organization: Organization name
            provider: Provider name
            billing_period: Billing period
            new_drop_id: Drop ID for the combined drop
            copied_files: Files copied from source drop
            uploaded_files: Files uploaded in upload phase
            
        Returns:
            Dictionary with update results
        """
        manifest_key = self._build_manifest_key(organization, provider, billing_period)
        
        try:
            # Create new manifest data
            manifest_data = {
                "version": "1.3.0",
                "current_drop_id": new_drop_id
            }
            
            # Upload updated manifest
            json_content = json.dumps(manifest_data, indent=4)
            
            self.s3_client.put_object(
                Bucket=self.bucket,
                Key=manifest_key,
                Body=json_content,
                ContentType='application/json'
            )
            
            print(f"    ✓ Updated manifest: {manifest_key}")
            
            return {
                'status': 'success',
                'manifest_key': manifest_key,
                'drop_id': new_drop_id,
                'total_files': len(copied_files) + len(uploaded_files)
            }
            
        except ClientError as e:
            error_msg = f"Failed to update manifest {manifest_key}: {str(e)}"
            print(f"    ❌ {error_msg}")
            
            return {
                'status': 'failed',
                'error': error_msg,
                'manifest_key': manifest_key
            }
    
    def validate_append_manifest_consistency(
        self,
        organization: str,
        provider: str,
        billing_period: str,
        expected_drop_id: str
    ) -> Dict[str, Any]:
        """
        Validate that manifest points to expected drop after append operation.
        
        Verifies that the manifest update was successful and the manifest
        points to the correct drop containing the combined files.
        
        Args:
            organization: Organization name
            provider: Provider name
            billing_period: Billing period
            expected_drop_id: Expected drop ID in manifest
            
        Returns:
            Validation results dictionary
        """
        manifest_key = self._build_manifest_key(organization, provider, billing_period)
        
        try:
            # Read current manifest
            response = self.s3_client.get_object(Bucket=self.bucket, Key=manifest_key)
            manifest_content = response['Body'].read().decode('utf-8')
            manifest_data = json.loads(manifest_content)
            
            current_drop_id = manifest_data.get('current_drop_id')
            
            if current_drop_id == expected_drop_id:
                return {
                    'valid': True,
                    'current_drop_id': current_drop_id,
                    'manifest_data': manifest_data
                }
            else:
                return {
                    'valid': False,
                    'error': f"Manifest points to {current_drop_id}, expected {expected_drop_id}",
                    'current_drop_id': current_drop_id,
                    'expected_drop_id': expected_drop_id
                }
                
        except ClientError as e:
            return {
                'valid': False,
                'error': f"Failed to read manifest {manifest_key}: {str(e)}"
            }
        except json.JSONDecodeError as e:
            return {
                'valid': False,
                'error': f"Invalid JSON in manifest {manifest_key}: {str(e)}"
            }
    
    def backup_source_manifest(
        self,
        organization: str,
        provider: str,
        billing_period: str,
        source_drop_id: str
    ) -> Dict[str, Any]:
        """
        Create backup of source manifest before append operation.
        
        Preserves the original manifest state in case rollback is needed.
        
        Args:
            organization: Organization name
            provider: Provider name
            billing_period: Billing period
            source_drop_id: Source drop ID being appended to
            
        Returns:
            Backup operation results
        """
        manifest_key = self._build_manifest_key(organization, provider, billing_period)
        backup_key = f"{manifest_key}.backup.{source_drop_id}"
        
        try:
            # Copy current manifest to backup location
            copy_source = {
                'Bucket': self.bucket,
                'Key': manifest_key
            }
            
            self.s3_client.copy_object(
                CopySource=copy_source,
                Bucket=self.bucket,
                Key=backup_key
            )
            
            return {
                'status': 'success',
                'backup_key': backup_key,
                'original_key': manifest_key
            }
            
        except ClientError as e:
            return {
                'status': 'failed',
                'error': f"Failed to backup manifest: {str(e)}",
                'backup_key': backup_key
            }
    
    def list_manifest_backups(
        self,
        organization: str,
        provider: str,
        billing_period: str
    ) -> List[Dict[str, Any]]:
        """
        List all manifest backup files for a provider/period.
        
        Helps with rollback operations and cleanup.
        
        Args:
            organization: Organization name
            provider: Provider name
            billing_period: Billing period
            
        Returns:
            List of backup file information
        """
        manifest_key = self._build_manifest_key(organization, provider, billing_period)
        backup_prefix = f"{manifest_key}.backup."
        
        try:
            response = self.s3_client.list_objects_v2(
                Bucket=self.bucket,
                Prefix=backup_prefix
            )
            
            backups = []
            if 'Contents' in response:
                for obj in response['Contents']:
                    key = obj['Key']
                    # Extract drop ID from backup filename
                    drop_id = key.split('.')[-1]  # Last part after final dot
                    
                    backups.append({
                        'backup_key': key,
                        'drop_id': drop_id,
                        'size': obj['Size'],
                        'last_modified': obj['LastModified'].isoformat()
                    })
            
            return sorted(backups, key=lambda x: x['last_modified'], reverse=True)
            
        except ClientError as e:
            print(f"Warning: Failed to list manifest backups: {str(e)}")
            return []
    
    def _build_manifest_key(self, organization: str, provider: str, billing_period: str) -> str:
        """Build S3 key for manifest.json file."""
        return f"{self.root_folder}/{organization}/{provider}/{billing_period}/manifest.json"