"""
Sync Manager for coordinating local cache and S3 operations.

Handles upload/download coordination with automatic versioning following
Single Responsibility Principle from CLAUDE.md.
"""

import hashlib
import json
from datetime import datetime
from typing import Optional

from src.log_context import get_logger
from .local_cache_manager import LocalCacheManager
from .version_manager import VersionManager
from .models import SyncStatus, UploadResult, LocalManifest


class SyncManager:
    """
    Coordinates synchronization between local cache and S3.

    Single responsibility: Upload/download coordination with versioning.
    """

    def __init__(self, cache_manager: LocalCacheManager, version_manager: VersionManager, s3_service):
        """Initialize with injected dependencies."""
        self.logger = get_logger(__name__)
        self.cache_manager = cache_manager
        self.version_manager = version_manager
        self.s3_service = s3_service

    def get_sync_status(self) -> SyncStatus:
        """
        Compare local cache with S3 to determine sync status.

        Returns:
            SyncStatus with comparison details and recommendation
        """
        try:
            # Get local cache info
            local_info = self.cache_manager.get_cache_info()

            if not local_info:
                # No local cache exists
                return SyncStatus(
                    is_synced=False,
                    local_checksum="",
                    remote_checksum="",
                    local_modified=datetime.min,
                    remote_modified=datetime.min,
                    recommendation="download",
                    local_ahead=False,
                    remote_ahead=True
                )

            # Get S3 manifest info
            manifest_info = self.version_manager.get_manifest()

            if not manifest_info:
                # No S3 manifest exists - local is ahead
                return SyncStatus(
                    is_synced=False,
                    local_checksum=local_info.checksum,
                    remote_checksum="",
                    local_modified=local_info.modified_time,
                    remote_modified=datetime.min,
                    recommendation="upload",
                    local_ahead=True,
                    remote_ahead=False
                )

            # Compare checksums
            is_synced = (local_info.checksum == manifest_info.sync_checksum and
                        not local_info.has_local_changes)

            if is_synced:
                recommendation = "synced"
                local_ahead = False
                remote_ahead = False
            elif local_info.has_local_changes:
                recommendation = "upload"
                local_ahead = True
                remote_ahead = False
            else:
                # Local is behind
                recommendation = "download"
                local_ahead = False
                remote_ahead = True

            return SyncStatus(
                is_synced=is_synced,
                local_checksum=local_info.checksum,
                remote_checksum=manifest_info.sync_checksum,
                local_modified=local_info.modified_time,
                remote_modified=manifest_info.last_sync,
                recommendation=recommendation,
                local_ahead=local_ahead,
                remote_ahead=remote_ahead
            )

        except Exception as e:
            self.logger.error(f"Failed to get sync status: {e}")
            return SyncStatus(
                is_synced=False,
                local_checksum="",
                remote_checksum="",
                local_modified=datetime.min,
                remote_modified=datetime.min,
                recommendation="unknown",
                local_ahead=False,
                remote_ahead=False
            )

    def upload_with_versioning(self, force: bool = False) -> UploadResult:
        """
        Upload local changes to S3 with automatic version backup.

        Args:
            force: Upload even if no local changes detected

        Returns:
            UploadResult with operation details
        """
        try:
            # Check if there are local changes
            cache_info = self.cache_manager.get_cache_info()
            if not cache_info:
                return UploadResult(
                    success=False,
                    backup_version="",
                    new_current_checksum="",
                    error_message="No local cache exists to upload"
                )

            if not cache_info.has_local_changes and not force:
                return UploadResult(
                    success=False,
                    backup_version="",
                    new_current_checksum=cache_info.checksum,
                    error_message="No local changes to upload (use --force to override)"
                )

            # Create backup of current S3 data before upload (if exists)
            backup_version = self.version_manager.create_version_backup("upload")

            # Upload local cache to S3 current
            local_path = self.cache_manager.cache_path
            current_key = f"{self.s3_service.get_config_value('reference_data_prefix')}/current.parquet"

            self.s3_service.s3_client.upload_file(
                str(local_path),
                self.s3_service.get_config_value("reference_data_bucket"),
                current_key
            )

            # Generate version ID for manifest (use backup version if created, otherwise generate new one)
            manifest_version = backup_version or self.version_manager._generate_version_id()

            # Update S3 manifest
            self.version_manager.update_manifest(
                version_id=manifest_version,
                operation="upload",
                checksum=cache_info.checksum
            )

            # Mark local cache as synced
            self.cache_manager.mark_synced(manifest_version, cache_info.checksum)

            if backup_version:
                self.logger.info(f"Upload completed - backup version: {backup_version}")
            else:
                self.logger.info(f"Initial upload completed - no backup needed")

            return UploadResult(
                success=True,
                backup_version=backup_version or "initial",
                new_current_checksum=cache_info.checksum,
                error_message=None
            )

        except Exception as e:
            error_msg = f"Upload failed: {e}"
            self.logger.error(error_msg)
            return UploadResult(
                success=False,
                backup_version="",
                new_current_checksum="",
                error_message=error_msg
            )

    def download_from_s3(self, force: bool = False) -> bool:
        """
        Download current S3 data to local cache.

        Args:
            force: Download even if local has changes

        Returns:
            True if download successful

        Raises:
            ValueError: If local changes detected and not forcing
            FileNotFoundError: If reference data not found in S3
            Exception: For other download failures
        """
        # Check if there are local changes that would be overwritten
        cache_info = self.cache_manager.get_cache_info()
        if cache_info and cache_info.has_local_changes and not force:
            raise ValueError("Local changes detected - use --force to overwrite")

        # Download S3 current to local cache
        local_path = self.cache_manager.cache_path
        local_path.parent.mkdir(parents=True, exist_ok=True)

        try:
            self.s3_service.download_reference_data_to_path(local_path)
        except FileNotFoundError:
            # Re-raise with more context
            raise FileNotFoundError(
                "No reference data found in S3. Upload data first with 'czbg data upload'"
            )
        except Exception as e:
            # Re-raise with more context
            raise RuntimeError(f"Failed to download from S3: {e}") from e

        # Get S3 manifest for sync tracking
        manifest_info = self.version_manager.get_manifest()
        if manifest_info:
            # Calculate checksum of downloaded file
            new_checksum = self._calculate_file_checksum(local_path)

            # Mark as synced
            self.cache_manager.mark_synced(
                manifest_info.current_version,
                new_checksum
            )

        self.logger.info("Download completed successfully")
        return True

    def _calculate_file_checksum(self, file_path) -> str:
        """Calculate MD5 checksum of a file."""
        md5_hash = hashlib.md5()
        with open(file_path, "rb") as f:
            for chunk in iter(lambda: f.read(4096), b""):
                md5_hash.update(chunk)
        return md5_hash.hexdigest()