"""
Manifest reading service for append operations.

Provides discovery of active drops from manifest.json files stored in S3.
Uses explicit dependency injection and follows CLAUDE.md principles.
"""

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


@dataclass
class DropInfo:
    """Information about a discovered drop."""
    drop_id: str
    organization: str
    provider: str
    billing_period: str
    s3_prefix: str  # Full S3 prefix to the drop folder
    manifest_data: Dict[str, Any]


@dataclass
class S3FileInfo:
    """Information about a file in S3."""
    key: str
    filename: str
    size_bytes: int
    last_modified: str


class ManifestReader:
    """
    Service for reading and parsing manifest.json files from S3.
    
    Single responsibility: Parse manifest files to discover active drops.
    Uses dependency injection for S3 client and configuration.
    """
    
    def __init__(self, s3_client, bucket: str, root_folder: str = "piedpiper-custom-billing"):
        """
        Initialize with explicit dependencies.
        
        Args:
            s3_client: Boto3 S3 client for operations
            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 find_active_drop(self, organization: str, provider: str, billing_period: str) -> Optional[DropInfo]:
        """
        Find the currently active drop for a provider/billing period.
        
        Reads manifest.json from S3 to discover the active drop ID,
        following the authoritative source principle from the design.
        
        Args:
            organization: Organization name
            provider: Provider name (AWS, GCP, Azure)
            billing_period: Billing period (YYYYMMDD-YYYYMMDD format)
            
        Returns:
            DropInfo if active drop found, None otherwise
        """
        manifest_key = self._build_manifest_s3_key(organization, provider, billing_period)
        
        try:
            # Download manifest.json from S3
            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)
            
            # Extract active drop ID
            current_drop_id = manifest_data.get('current_drop_id')
            if not current_drop_id:
                return None
            
            # Build drop info
            drop_prefix = self._build_drop_s3_prefix(organization, provider, billing_period, current_drop_id)
            
            return DropInfo(
                drop_id=current_drop_id,
                organization=organization,
                provider=provider,
                billing_period=billing_period,
                s3_prefix=drop_prefix,
                manifest_data=manifest_data
            )
            
        except ClientError as e:
            error_code = e.response['Error']['Code']
            if error_code == 'NoSuchKey':
                # Manifest doesn't exist - no active drop
                return None
            else:
                # Other S3 error - propagate
                raise
    
    def list_drop_files(self, drop_info: DropInfo) -> List[S3FileInfo]:
        """
        List all data files in a drop folder.
        
        Discovers data-export-NNNN.csv.gz files in the active drop,
        excluding manifest.json and other metadata files.
        
        Args:
            drop_info: Drop information from find_active_drop()
            
        Returns:
            List of S3FileInfo for data files in the drop
        """
        try:
            # List objects in the drop folder
            response = self.s3_client.list_objects_v2(
                Bucket=self.bucket,
                Prefix=drop_info.s3_prefix
            )
            
            files = []
            if 'Contents' in response:
                for obj in response['Contents']:
                    key = obj['Key']
                    filename = key.split('/')[-1]  # Extract filename from full S3 key
                    
                    # Only include data-export files, exclude manifest and other metadata
                    if filename.startswith('data-export-') and filename.endswith('.csv.gz'):
                        files.append(S3FileInfo(
                            key=key,
                            filename=filename,
                            size_bytes=obj['Size'],
                            last_modified=obj['LastModified'].isoformat()
                        ))
            
            # Sort by filename to ensure consistent ordering
            files.sort(key=lambda f: f.filename)
            return files
            
        except ClientError as e:
            # Log error and return empty list for robustness
            print(f"Warning: Failed to list files in drop {drop_info.drop_id}: {str(e)}")
            return []
    
    def get_next_file_sequence(self, drop_files: List[S3FileInfo]) -> int:
        """
        Calculate the next file sequence number for new uploads.
        
        Examines existing data-export-NNNN.csv.gz files to determine
        the next sequential number to use for new files.
        
        Args:
            drop_files: List of existing files from list_drop_files()
            
        Returns:
            Next sequence number to use (starting from 1 if no files exist)
        """
        if not drop_files:
            return 1
        
        max_sequence = 0
        for file_info in drop_files:
            # Extract sequence number from filename: data-export-NNNN.csv.gz
            if file_info.filename.startswith('data-export-'):
                try:
                    # Extract the NNNN part
                    sequence_part = file_info.filename[12:16]  # data-export-NNNN
                    sequence_num = int(sequence_part)
                    max_sequence = max(max_sequence, sequence_num)
                except (ValueError, IndexError):
                    # Skip files with invalid sequence numbers
                    continue
        
        return max_sequence + 1
    
    def _build_manifest_s3_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"
    
    def _build_drop_s3_prefix(self, organization: str, provider: str, billing_period: str, drop_id: str) -> str:
        """Build S3 prefix for drop folder."""
        return f"{self.root_folder}/{organization}/{provider}/{billing_period}/{drop_id}/"