"""
Path management service for append operations.

Handles S3 path translations, filename conflict resolution, and drop folder management.
Uses dependency injection and follows CLAUDE.md single responsibility principles.
"""

from typing import List, Dict, Any, Optional, Set
from dataclasses import dataclass
from .manifest_reader import S3FileInfo, DropInfo


@dataclass
class PathTranslation:
    """Result of path translation operation."""
    source_key: str
    target_key: str
    filename: str
    conflicts: bool = False
    resolution_strategy: str = ""


class PathManager:
    """
    Service for managing S3 paths during append operations.
    
    Single responsibility: Handle path translations and conflict resolution.
    Provides clean separation between path logic and copy operations.
    """
    
    def __init__(self, root_folder: str = "piedpiper-custom-billing"):
        """
        Initialize path manager with configuration.
        
        Args:
            root_folder: Root folder for billing data in S3
        """
        self.root_folder = root_folder
    
    def translate_paths_for_append(
        self, 
        source_files: List[S3FileInfo], 
        target_organization: str,
        target_provider: str,
        target_billing_period: str,
        target_drop_id: str
    ) -> List[PathTranslation]:
        """
        Translate source file paths to target drop paths.
        
        Creates path mappings for copying existing files to a new drop,
        maintaining the original filename structure while updating the drop location.
        
        Args:
            source_files: List of source files to translate
            target_organization: Target organization name
            target_provider: Target provider name  
            target_billing_period: Target billing period
            target_drop_id: Target drop ID
            
        Returns:
            List of PathTranslation objects with source->target mappings
        """
        target_prefix = self.build_drop_prefix(
            target_organization, target_provider, target_billing_period, target_drop_id
        )
        
        translations = []
        for file_info in source_files:
            target_key = f"{target_prefix}{file_info.filename}"
            
            translations.append(PathTranslation(
                source_key=file_info.key,
                target_key=target_key,
                filename=file_info.filename
            ))
        
        return translations
    
    def resolve_filename_conflicts(
        self, 
        existing_files: List[S3FileInfo], 
        new_files: List[S3FileInfo],
        next_sequence_start: int
    ) -> Dict[str, str]:
        """
        Resolve filename conflicts between existing and new files.
        
        When appending data, new files need to use sequence numbers that
        don't conflict with existing files from the source drop.
        
        Args:
            existing_files: Files that will be copied from source drop
            new_files: New files that will be uploaded
            next_sequence_start: Starting sequence number for new files
            
        Returns:
            Dictionary mapping original new filenames to conflict-free names
        """
        # Extract existing filenames and sequence numbers
        existing_sequences = set()
        for file_info in existing_files:
            sequence = self._extract_sequence_number(file_info.filename)
            if sequence is not None:
                existing_sequences.add(sequence)
        
        # Generate conflict-free names for new files
        filename_mapping = {}
        current_sequence = next_sequence_start
        
        for file_info in new_files:
            # Ensure we don't conflict with existing sequences
            while current_sequence in existing_sequences:
                current_sequence += 1
            
            new_filename = f"data-export-{current_sequence:04d}.csv.gz"
            filename_mapping[file_info.filename] = new_filename
            
            existing_sequences.add(current_sequence)
            current_sequence += 1
        
        return filename_mapping
    
    def detect_path_conflicts(
        self, 
        translations: List[PathTranslation], 
        existing_target_files: Optional[List[S3FileInfo]] = None
    ) -> List[PathTranslation]:
        """
        Detect potential path conflicts in translation mappings.
        
        Identifies cases where multiple sources map to the same target,
        or where targets would overwrite existing files.
        
        Args:
            translations: List of path translations to check
            existing_target_files: Optional list of files already in target location
            
        Returns:
            List of translations with conflict information updated
        """
        # Track target keys to detect duplicates
        target_keys = set()
        duplicate_targets = set()
        
        for translation in translations:
            if translation.target_key in target_keys:
                duplicate_targets.add(translation.target_key)
            else:
                target_keys.add(translation.target_key)
        
        # Track existing files in target location
        existing_target_keys = set()
        if existing_target_files:
            existing_target_keys = {f.key for f in existing_target_files}
        
        # Update conflict information
        updated_translations = []
        for translation in translations:
            conflicts = False
            resolution_strategy = ""
            
            # Check for duplicate targets
            if translation.target_key in duplicate_targets:
                conflicts = True
                resolution_strategy += "duplicate_target;"
            
            # Check for existing file conflicts
            if translation.target_key in existing_target_keys:
                conflicts = True
                resolution_strategy += "overwrite_existing;"
            
            updated_translations.append(PathTranslation(
                source_key=translation.source_key,
                target_key=translation.target_key,
                filename=translation.filename,
                conflicts=conflicts,
                resolution_strategy=resolution_strategy.rstrip(';')
            ))
        
        return updated_translations
    
    def generate_safe_target_paths(
        self, 
        source_files: List[S3FileInfo],
        target_organization: str,
        target_provider: str,
        target_billing_period: str,
        target_drop_id: str,
        existing_target_files: Optional[List[S3FileInfo]] = None
    ) -> List[PathTranslation]:
        """
        Generate conflict-free target paths for source files.
        
        Combines path translation with conflict detection and resolution
        to provide safe target paths for copy operations.
        
        Args:
            source_files: Files to generate paths for
            target_organization: Target organization
            target_provider: Target provider
            target_billing_period: Target billing period
            target_drop_id: Target drop ID
            existing_target_files: Optional existing files in target location
            
        Returns:
            List of safe PathTranslation objects
        """
        # Generate initial translations
        translations = self.translate_paths_for_append(
            source_files, target_organization, target_provider, 
            target_billing_period, target_drop_id
        )
        
        # Detect conflicts
        translations_with_conflicts = self.detect_path_conflicts(translations, existing_target_files)
        
        # Resolve conflicts by generating new target keys
        safe_translations = []
        used_filenames = set()
        
        # Track filenames from existing target files
        if existing_target_files:
            for file_info in existing_target_files:
                used_filenames.add(file_info.filename)
        
        for translation in translations_with_conflicts:
            if not translation.conflicts:
                # No conflicts - use as-is
                safe_translations.append(translation)
                used_filenames.add(translation.filename)
            else:
                # Generate new filename to avoid conflicts
                safe_filename = self._generate_safe_filename(translation.filename, used_filenames)
                target_prefix = self.build_drop_prefix(
                    target_organization, target_provider, target_billing_period, target_drop_id
                )
                
                safe_translations.append(PathTranslation(
                    source_key=translation.source_key,
                    target_key=f"{target_prefix}{safe_filename}",
                    filename=safe_filename,
                    conflicts=False,
                    resolution_strategy="filename_renamed"
                ))
                used_filenames.add(safe_filename)
        
        return safe_translations
    
    def _generate_safe_filename(self, original_filename: str, used_filenames: Set[str]) -> str:
        """
        Generate a safe filename that doesn't conflict with used filenames.
        
        Args:
            original_filename: Original filename that has conflicts
            used_filenames: Set of already used filenames
            
        Returns:
            Safe filename that doesn't conflict
        """
        # For data-export files, increment the sequence number
        if original_filename.startswith('data-export-') and original_filename.endswith('.csv.gz'):
            sequence = self._extract_sequence_number(original_filename)
            if sequence is not None:
                # Find next available sequence number
                new_sequence = sequence
                while True:
                    new_filename = f"data-export-{new_sequence:04d}.csv.gz"
                    if new_filename not in used_filenames:
                        return new_filename
                    new_sequence += 1
        
        # Fallback: append timestamp or counter
        import time
        timestamp = int(time.time())
        counter = 1
        
        base_name = original_filename.rsplit('.', 2)[0]  # Remove .csv.gz
        extension = '.csv.gz'
        
        while True:
            new_filename = f"{base_name}_{timestamp}_{counter:03d}{extension}"
            if new_filename not in used_filenames:
                return new_filename
            counter += 1
    
    def build_drop_prefix(
        self, 
        organization: str, 
        provider: str, 
        billing_period: str, 
        drop_id: str
    ) -> str:
        """
        Build complete S3 prefix for a drop folder.
        
        Args:
            organization: Organization name
            provider: Provider name
            billing_period: Billing period (YYYYMMDD-YYYYMMDD)
            drop_id: Drop ID timestamp
            
        Returns:
            Complete S3 prefix ending with '/'
        """
        return f"{self.root_folder}/{organization}/{provider}/{billing_period}/{drop_id}/"
    
    def build_manifest_path(
        self, 
        organization: str, 
        provider: str, 
        billing_period: str
    ) -> str:
        """
        Build S3 path for manifest.json file.
        
        Args:
            organization: Organization name
            provider: Provider name
            billing_period: Billing period
            
        Returns:
            Complete S3 key for manifest.json
        """
        return f"{self.root_folder}/{organization}/{provider}/{billing_period}/manifest.json"
    
    def validate_path_structure(self, organization: str, provider: str, billing_period: str) -> Dict[str, Any]:
        """
        Validate that path components are properly formatted.
        
        Checks for valid characters, length limits, and format compliance
        to prevent S3 key issues.
        
        Args:
            organization: Organization name
            provider: Provider name
            billing_period: Billing period string
            
        Returns:
            Validation results with 'valid' boolean and 'issues' list
        """
        issues = []
        
        # Check organization name
        if not organization or not organization.strip():
            issues.append("Organization name cannot be empty")
        elif len(organization) > 100:
            issues.append("Organization name too long (max 100 characters)")
        elif not organization.replace('-', '').replace('_', '').isalnum():
            issues.append("Organization name contains invalid characters")
        
        # Check provider name
        valid_providers = {"AWS", "GCP", "Azure", "Anthropic", "Databricks", "Datadog", "Fastly", "MongoDB", "OpenAI", "Oracle Cloud", "Redis", "Snowflake", "Sumologic"}
        if provider not in valid_providers:
            issues.append(f"Invalid provider '{provider}'. Must be one of: {', '.join(sorted(valid_providers))}")
        
        # Check billing period format (YYYYMMDD-YYYYMMDD)
        if not self._validate_billing_period_format(billing_period):
            issues.append(f"Invalid billing period format '{billing_period}'. Expected: YYYYMMDD-YYYYMMDD")
        
        return {
            'valid': len(issues) == 0,
            'issues': issues
        }
    
    def _extract_sequence_number(self, filename: str) -> Optional[int]:
        """Extract sequence number from data-export-NNNN.csv.gz filename."""
        if not filename.startswith('data-export-') or not filename.endswith('.csv.gz'):
            return None
        
        try:
            # Extract NNNN part from data-export-NNNN.csv.gz
            sequence_part = filename[12:16]  # Characters 12-15
            return int(sequence_part)
        except (ValueError, IndexError):
            return None
    
    def _validate_billing_period_format(self, billing_period: str) -> bool:
        """Validate billing period matches YYYYMMDD-YYYYMMDD format."""
        if not billing_period or '-' not in billing_period:
            return False
        
        parts = billing_period.split('-')
        if len(parts) != 2:
            return False
        
        start_date, end_date = parts
        
        # Check format and basic date validity
        if len(start_date) != 8 or len(end_date) != 8:
            return False
        
        if not (start_date.isdigit() and end_date.isdigit()):
            return False
        
        # Basic month/day range validation
        start_month = int(start_date[4:6])
        start_day = int(start_date[6:8])
        end_month = int(end_date[4:6])
        end_day = int(end_date[6:8])
        
        if not (1 <= start_month <= 12 and 1 <= end_month <= 12):
            return False
        
        if not (1 <= start_day <= 31 and 1 <= end_day <= 31):
            return False
        
        return True