"""
Provider copy orchestrator for S3-to-S3 organization data copying.

Orchestrates discovery of provider data in source org, validation,
S3-to-S3 copy operations, manifest updates, and connection management.
"""

from typing import List, Optional, Dict, Any
from ..models.provider_copy_models import (
    ProviderDataInventory,
    BillingPeriodInfo,
    ProviderCopyResult
)
from ..append.manifest_reader import ManifestReader, S3FileInfo
from ..append.s3_file_copier import S3FileCopier
from ..append.manifest_manager import ManifestManager
from src.upload.services.connection_orchestrator import ConnectionOrchestrator
from ..s3.formatter import S3Formatter


class ProviderCopyOrchestrator:
    """
    Orchestrate copying provider data from source to target organization.

    Single responsibility: Copy complete provider billing data between orgs.
    Uses S3-to-S3 operations for efficiency (no local download/upload).
    All methods <50 lines per CLAUDE.md principles.
    """

    def __init__(
        self,
        s3_file_copier: S3FileCopier,
        manifest_reader: ManifestReader,
        manifest_manager: ManifestManager,
        connection_orchestrator: ConnectionOrchestrator,
        s3_formatter: S3Formatter,
        s3_bucket: str,
        s3_root_folder: str = "piedpiper-custom-billing"
    ):
        """
        Initialize with explicit dependencies.

        Args:
            s3_file_copier: Service for S3-to-S3 copy operations
            manifest_reader: Service for reading manifest files
            manifest_manager: Service for updating manifest files
            connection_orchestrator: Service for connection management
            s3_formatter: Service for S3 formatting utilities
            s3_bucket: S3 bucket name
            s3_root_folder: Root folder for billing data
        """
        self.s3_file_copier = s3_file_copier
        self.manifest_reader = manifest_reader
        self.manifest_manager = manifest_manager
        self.connection_orchestrator = connection_orchestrator
        self.s3_formatter = s3_formatter
        self.s3_bucket = s3_bucket
        self.s3_root_folder = s3_root_folder

    def _get_provider_folder(
        self,
        provider: str,
        connection_suffix: Optional[str] = None
    ) -> str:
        """
        Get S3 provider folder name with optional suffix.

        Args:
            provider: Provider name (e.g., "AWS")
            connection_suffix: Optional suffix (e.g., "Anomalies")

        Returns:
            Provider folder: "AWS" or "AWS-Anomalies"
        """
        if connection_suffix:
            return f"{provider}-{connection_suffix}"
        return provider

    def copy_provider_to_org(
        self,
        source_org: str,
        target_org: str,
        provider: str,
        billing_periods: Optional[List[str]] = None,
        dry_run: bool = False,
        create_connection: bool = True,
        recreate_connection: bool = False,
        connection_suffix: Optional[str] = None
    ) -> ProviderCopyResult:
        """
        Copy provider billing data from source to target organization.

        Main orchestration method - coordinates discovery, validation,
        copy operations, manifest updates, and connection management.

        Args:
            source_org: Source organization name
            target_org: Target organization name
            provider: Provider to copy (AWS, Azure, GCP, etc)
            billing_periods: Optional list of periods (None = all)
            dry_run: Show plan without executing
            create_connection: Create/validate CloudZero connection
            recreate_connection: Delete and recreate target connection before copy
            connection_suffix: Optional suffix for named connections (e.g., "Anomalies")

        Returns:
            ProviderCopyResult with copy outcomes
        """
        print(f"🔍 Discovering {provider} data in {source_org}...")

        # Discovery phase
        inventory = self._discover_source_provider_data(
            source_org, provider, billing_periods, connection_suffix
        )

        if inventory.is_empty():
            return self._create_empty_result(source_org, target_org, provider)

        print(f"   📊 Found {len(inventory.billing_periods)} billing periods")
        print(f"   📁 Total files: {inventory.total_files}")
        print(f"   💾 Total size: {inventory.total_size_bytes / (1024**3):.2f} GB")

        # Validation phase
        validation_errors = self._validate_copy_preconditions(
            inventory, target_org
        )

        if validation_errors:
            return self._create_validation_failure_result(
                source_org, target_org, provider, validation_errors
            )

        if dry_run:
            return self._create_dry_run_result(inventory, target_org)

        # Recreation phase (if requested)
        if recreate_connection:
            recreation_result = self._recreate_target_connection(
                target_org, provider, dry_run
            )
            if recreation_result["status"] == "failed":
                return self._create_recreation_failure_result(
                    source_org, target_org, provider, recreation_result
                )

        # Execution phase
        return self._execute_copy_workflow(
            inventory, target_org, create_connection, connection_suffix
        )

    def _discover_source_provider_data(
        self,
        source_org: str,
        provider: str,
        billing_periods: Optional[List[str]] = None,
        connection_suffix: Optional[str] = None
    ) -> ProviderDataInventory:
        """
        Discover all billing periods for provider in source org.

        Reads manifest files from source org to find all available
        billing periods and their files.
        """
        # Get all billing periods from source org
        # For now, we need to scan S3 to find billing periods
        # In future, could have org-level manifest listing all periods
        discovered_periods = self._scan_source_billing_periods(
            source_org, provider, connection_suffix
        )

        # Filter to requested billing periods if specified
        if billing_periods:
            discovered_periods = [
                p for p in discovered_periods
                if p.billing_period in billing_periods
            ]

        total_files = sum(len(p.files) for p in discovered_periods)
        total_size = sum(p.total_size_bytes for p in discovered_periods)

        return ProviderDataInventory(
            source_org=source_org,
            provider=provider,
            billing_periods=discovered_periods,
            total_files=total_files,
            total_size_bytes=total_size
        )

    def _scan_source_billing_periods(
        self,
        source_org: str,
        provider: str,
        connection_suffix: Optional[str] = None
    ) -> List[BillingPeriodInfo]:
        """
        Scan S3 to find all billing periods for provider in source org.

        Lists S3 prefixes under source org/provider to discover periods,
        then reads manifest for each period.
        """
        import boto3

        periods = []
        provider_folder = self._get_provider_folder(provider, connection_suffix)
        prefix = f"{self.s3_root_folder}/{source_org}/{provider_folder}/"

        try:
            s3_client = boto3.client('s3')
            response = s3_client.list_objects_v2(
                Bucket=self.s3_bucket,
                Prefix=prefix,
                Delimiter='/'
            )

            # Extract billing period folders
            for common_prefix in response.get('CommonPrefixes', []):
                period_path = common_prefix['Prefix']
                # Extract billing period from path
                period_name = period_path.rstrip('/').split('/')[-1]

                # Try to read manifest for this period
                period_info = self._discover_billing_period_data(
                    source_org, provider, period_name
                )

                if period_info:
                    periods.append(period_info)

        except Exception as e:
            print(f"   ⚠️  Error scanning billing periods: {str(e)}")

        return periods

    def _discover_billing_period_data(
        self, source_org: str, provider: str, billing_period: str
    ) -> Optional[BillingPeriodInfo]:
        """
        Discover data for single billing period from manifest.

        Reads manifest.json to get active drop and file list.
        """
        drop_info = self.manifest_reader.find_active_drop(
            organization=source_org,
            provider=provider,
            billing_period=billing_period
        )

        if not drop_info:
            return None

        # Get file list from drop
        files = self.manifest_reader.list_drop_files(drop_info)

        total_size = sum(f.size_bytes for f in files)
        total_rows = drop_info.manifest_data.get('total_rows', 0)

        return BillingPeriodInfo(
            billing_period=billing_period,
            drop_id=drop_info.drop_id,
            files=files,
            manifest_data=drop_info.manifest_data,
            total_rows=total_rows,
            total_size_bytes=total_size
        )

    def _validate_copy_preconditions(
        self, inventory: ProviderDataInventory, target_org: str
    ) -> List[str]:
        """
        Validate copy can proceed - return list of errors.

        Checks source data exists, target is accessible, no conflicts.
        """
        errors = []

        # Check source has data
        if inventory.is_empty():
            errors.append(
                f"No {inventory.provider} data found in {inventory.source_org}"
            )

        # Check target org is accessible
        # This would validate target org API key exists
        # For now, assume accessible if no explicit error

        return errors

    def _execute_copy_workflow(
        self,
        inventory: ProviderDataInventory,
        target_org: str,
        create_connection: bool,
        connection_suffix: Optional[str] = None
    ) -> ProviderCopyResult:
        """
        Execute complete copy workflow.

        Generates target drop ID, copies files, updates manifests,
        creates connection.
        """
        # Generate new drop ID for target
        target_drop_id = self.s3_formatter.generate_drop_id()

        print(f"\n🚀 Starting copy to {target_org}")
        print(f"   📍 Target drop ID: {target_drop_id}")

        # Copy files for all billing periods
        copy_results = self._copy_all_billing_periods(
            inventory, target_org, target_drop_id, connection_suffix
        )

        # Update manifests for target org
        manifest_updated = self._update_all_target_manifests(
            inventory, target_org, target_drop_id, connection_suffix
        )

        # Create/validate connection if requested
        connection_status = "skipped"
        if create_connection:
            connection_status = self._ensure_target_connection(
                target_org, inventory.provider, connection_suffix
            )

        return self._create_success_result(
            inventory, target_org, target_drop_id,
            copy_results, manifest_updated, connection_status
        )

    def _copy_all_billing_periods(
        self,
        inventory: ProviderDataInventory,
        target_org: str,
        target_drop_id: str,
        connection_suffix: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Copy files for all billing periods.

        Uses parallel copy for each billing period.
        """
        all_results = {
            'files_copied': 0,
            'files_failed': 0,
            'errors': []
        }

        for period_info in inventory.billing_periods:
            print(f"\n   📅 Copying {period_info.billing_period}")
            print(f"      Files: {len(period_info.files)}")

            period_results = self._copy_billing_period_files(
                period_info, inventory.source_org, target_org, target_drop_id,
                inventory.provider, connection_suffix
            )

            all_results['files_copied'] += len([
                r for r in period_results if r.success
            ])
            all_results['files_failed'] += len([
                r for r in period_results if not r.success
            ])

        return all_results

    def _copy_billing_period_files(
        self,
        period_info: BillingPeriodInfo,
        source_org: str,
        target_org: str,
        target_drop_id: str,
        provider: str,
        connection_suffix: Optional[str] = None
    ):
        """Copy files for single billing period."""
        # Build target prefix
        target_prefix = self._build_target_prefix(
            target_org,
            provider,
            period_info.billing_period,
            target_drop_id,
            connection_suffix
        )

        # Ensure files have source org and correct prefix
        files_with_source = self._prepare_files_for_copy(
            period_info.files, source_org, provider, period_info, connection_suffix
        )

        # Execute parallel copy
        return self.s3_file_copier.copy_files_parallel(
            source_files=files_with_source,
            target_drop_prefix=target_prefix,
            skip_existing=True
        )

    def _prepare_files_for_copy(
        self,
        files: List[S3FileInfo],
        source_org: str,
        provider: str,
        period_info: BillingPeriodInfo,
        connection_suffix: Optional[str] = None
    ) -> List[S3FileInfo]:
        """
        Prepare file list for copy with correct source S3 keys.

        Ensures each file has full S3 key from source org.
        """
        prepared_files = []
        provider_folder = self._get_provider_folder(provider, connection_suffix)

        for file_info in files:
            # Build full source S3 key if not already present
            if not file_info.key.startswith(self.s3_root_folder):
                source_key = f"{self.s3_root_folder}/{source_org}/{provider_folder}/" \
                           f"{period_info.billing_period}/" \
                           f"{period_info.drop_id}/{file_info.filename}"
            else:
                source_key = file_info.key

            prepared_file = S3FileInfo(
                key=source_key,
                filename=file_info.filename,
                size_bytes=file_info.size_bytes,
                last_modified=file_info.last_modified
            )
            prepared_files.append(prepared_file)

        return prepared_files

    def _update_all_target_manifests(
        self,
        inventory: ProviderDataInventory,
        target_org: str,
        target_drop_id: str,
        connection_suffix: Optional[str] = None
    ) -> bool:
        """Update manifest.json for each billing period in target org."""
        all_updated = True

        for period_info in inventory.billing_periods:
            updated = self._update_target_manifest(
                target_org,
                inventory.provider,
                period_info,
                target_drop_id,
                connection_suffix
            )
            if not updated:
                all_updated = False
                print(f"      ⚠️  Manifest update failed for {period_info.billing_period}")

        return all_updated

    def _update_target_manifest(
        self,
        target_org: str,
        provider: str,
        period_info: BillingPeriodInfo,
        target_drop_id: str,
        connection_suffix: Optional[str] = None
    ) -> bool:
        """Update manifest.json for single billing period."""
        try:
            import boto3
            import json
            from botocore.exceptions import ClientError

            # Create manifest data
            manifest_data = self.s3_formatter.create_manifest_data(target_drop_id)

            # Build manifest S3 key with optional suffix
            provider_folder = self._get_provider_folder(provider, connection_suffix)
            manifest_key = f"{self.s3_root_folder}/{target_org}/{provider_folder}/{period_info.billing_period}/manifest.json"

            # Upload manifest to S3
            s3_client = boto3.client('s3')
            s3_client.put_object(
                Bucket=self.s3_bucket,
                Key=manifest_key,
                Body=json.dumps(manifest_data, indent=4),
                ContentType='application/json'
            )

            print(f"      ✓ Manifest updated for {period_info.billing_period}")
            return True

        except (ClientError, Exception) as e:
            print(f"      ❌ Manifest update failed: {str(e)}")
            return False

    def _create_manifest_entry(
        self, period_info: BillingPeriodInfo, target_drop_id: str
    ) -> Dict[str, Any]:
        """Create manifest entry for copied files with new drop ID."""
        return {
            'drop_id': target_drop_id,
            'billing_period': period_info.billing_period,
            'files': [
                {
                    'filename': f.filename,
                    'size_bytes': f.size_bytes
                }
                for f in period_info.files
            ],
            'total_rows': period_info.total_rows,
            'total_size_bytes': period_info.total_size_bytes
        }

    def _ensure_target_connection(
        self,
        target_org: str,
        provider: str,
        connection_suffix: Optional[str] = None
    ) -> str:
        """
        Ensure CloudZero connection exists for target org.

        Args:
            target_org: Target organization name
            provider: Provider name
            connection_suffix: Optional suffix for named connections

        Returns:
            Connection status: "created", "validated", or "failed".
        """
        try:
            # Use connection orchestrator to ensure connection
            s3_config = {
                'bucket_name': self.s3_bucket,
                'root_folder': self.s3_root_folder,
                'org_folder': target_org
            }

            result = self.connection_orchestrator.ensure_connections_exist(
                organization=target_org,
                providers=[provider],
                s3_config=s3_config,
                connection_suffix=connection_suffix
            )

            if result.auto_created_connections:
                print(f"      ✓ Connection created for {provider}")
                return "created"
            else:
                print(f"      ✓ Connection validated for {provider}")
                return "validated"

        except Exception as e:
            print(f"      ❌ Connection management failed: {str(e)}")
            return "failed"

    def _recreate_target_connection(
        self, target_org: str, provider: str, dry_run: bool
    ) -> Dict[str, Any]:
        """
        Recreate CloudZero connection and delete S3 folder for target org.

        This deletes the existing connection and S3 folder for the provider,
        then creates a fresh connection. Used for clean slate scenarios.

        Args:
            target_org: Target organization name
            provider: Provider to recreate
            dry_run: If True, only show what would be done

        Returns:
            Recreation result dict with status and details
        """
        try:
            print(f"   🔄 Recreating {provider} connection for {target_org}...")

            s3_config = {
                'bucket_name': self.s3_bucket,
                'root_folder': self.s3_root_folder,
                'org_folder': target_org
            }

            result = self.connection_orchestrator.recreate_organization_connections(
                organization=target_org,
                providers=[provider],
                s3_config=s3_config,
                dry_run=dry_run
            )

            if result["status"] == "success":
                print(f"      ✓ Connection recreated for {provider}")
            else:
                print(f"      ❌ Recreation failed: {result.get('error_message', 'Unknown error')}")

            return result

        except Exception as e:
            print(f"      ❌ Connection recreation failed: {str(e)}")
            return {
                "status": "failed",
                "organization": target_org,
                "error_message": str(e)
            }

    def _build_target_prefix(
        self,
        target_org: str,
        provider: str,
        billing_period: str,
        drop_id: str,
        connection_suffix: Optional[str] = None
    ) -> str:
        """Build S3 prefix for target files."""
        provider_folder = self._get_provider_folder(provider, connection_suffix)
        return f"{self.s3_root_folder}/{target_org}/{provider_folder}/{billing_period}/{drop_id}/"

    def _create_empty_result(
        self, source_org: str, target_org: str, provider: str
    ) -> ProviderCopyResult:
        """Create result for empty source (no data found)."""
        return ProviderCopyResult(
            source_org=source_org,
            target_org=target_org,
            provider=provider,
            status="failed",
            copy_errors=[f"No {provider} data found in {source_org}"]
        )

    def _create_validation_failure_result(
        self,
        source_org: str,
        target_org: str,
        provider: str,
        errors: List[str]
    ) -> ProviderCopyResult:
        """Create result for validation failures."""
        return ProviderCopyResult(
            source_org=source_org,
            target_org=target_org,
            provider=provider,
            status="failed",
            copy_errors=errors
        )

    def _create_recreation_failure_result(
        self,
        source_org: str,
        target_org: str,
        provider: str,
        recreation_result: Dict[str, Any]
    ) -> ProviderCopyResult:
        """Create result for connection recreation failures."""
        error_msg = recreation_result.get("error_message", "Connection recreation failed")
        return ProviderCopyResult(
            source_org=source_org,
            target_org=target_org,
            provider=provider,
            status="failed",
            copy_errors=[f"Recreation failed: {error_msg}"]
        )

    def _create_dry_run_result(
        self, inventory: ProviderDataInventory, target_org: str
    ) -> ProviderCopyResult:
        """Create result for dry-run mode."""
        return ProviderCopyResult(
            source_org=inventory.source_org,
            target_org=target_org,
            provider=inventory.provider,
            status="success",
            billing_periods_copied=inventory.get_billing_period_names(),
            files_copied=inventory.total_files,
            total_size_bytes=inventory.total_size_bytes
        )

    def _create_success_result(
        self,
        inventory: ProviderDataInventory,
        target_org: str,
        target_drop_id: str,
        copy_results: Dict[str, Any],
        manifest_updated: bool,
        connection_status: str
    ) -> ProviderCopyResult:
        """Create result for successful copy."""
        files_copied = copy_results['files_copied']
        files_failed = copy_results['files_failed']

        if files_failed == 0:
            status = "success"
        elif files_copied > 0:
            status = "partial"
        else:
            status = "failed"

        return ProviderCopyResult(
            source_org=inventory.source_org,
            target_org=target_org,
            provider=inventory.provider,
            status=status,
            billing_periods_copied=inventory.get_billing_period_names(),
            files_copied=files_copied,
            files_failed=files_failed,
            total_size_bytes=inventory.total_size_bytes,
            target_drop_id=target_drop_id,
            copy_errors=copy_results['errors'],
            manifest_updated=manifest_updated,
            connection_status=connection_status
        )


class ProviderCopyOrchestratorFactory:
    """Factory for creating ProviderCopyOrchestrator with dependencies."""

    @staticmethod
    def create() -> ProviderCopyOrchestrator:
        """Create orchestrator with all dependencies wired up."""
        from src.services.environment_service import EnvironmentService
        from ..s3.formatter import S3Formatter
        import boto3

        # Get S3 config
        env_service = EnvironmentService()
        s3_config = env_service.get_s3_config()

        # Create S3 client
        s3_client = boto3.client('s3', region_name=s3_config['region'])

        # Create services
        from ..append.s3_file_copier import S3FileCopier
        s3_file_copier = S3FileCopier(
            s3_client=s3_client,
            bucket=s3_config['bucket'],
            max_workers=5
        )

        from ..append.manifest_reader import ManifestReader
        manifest_reader = ManifestReader(
            s3_client=s3_client,
            bucket=s3_config['bucket'],
            root_folder="piedpiper-custom-billing"
        )

        from ..append.manifest_manager import ManifestManager
        manifest_manager = ManifestManager(
            s3_client=s3_client,
            bucket=s3_config['bucket'],
            root_folder="piedpiper-custom-billing"
        )

        connection_orchestrator = ConnectionOrchestrator()
        s3_formatter = S3Formatter()

        # Create and return orchestrator
        return ProviderCopyOrchestrator(
            s3_file_copier=s3_file_copier,
            manifest_reader=manifest_reader,
            manifest_manager=manifest_manager,
            connection_orchestrator=connection_orchestrator,
            s3_formatter=s3_formatter,
            s3_bucket=s3_config['bucket'],
            s3_root_folder="piedpiper-custom-billing"
        )
