"""Synchronization functionality for mem8."""

import shutil
from pathlib import Path
from typing import Dict, List, Any
import filecmp
from datetime import datetime

from .config import Config
from .utils import ensure_directory_exists


class SyncManager:
    """Manages synchronization between local and shared memory."""
    
    def __init__(self, config: Config):
        """Initialize sync manager."""
        self.config = config
        self.sync_metadata_file = config.data_dir / "sync_metadata.json"
    
    def sync_memory(
        self, 
        direction: str = 'both',
        dry_run: bool = False
    ) -> Dict[str, Any]:
        """Synchronize memory between local and shared locations."""
        try:
            if not self.config.shared_dir or not self.config.shared_dir.exists():
                return {
                    'success': False,
                    'error': 'Shared directory not configured or not accessible'
                }
            
            local_memory = self.config.memory_dir
            shared_memory = self.config.shared_dir / "memory"
            
            if not local_memory.exists():
                ensure_directory_exists(local_memory)
            
            if not shared_memory.exists():
                ensure_directory_exists(shared_memory)
            
            summary = {
                'pulled': 0,
                'pushed': 0,
                'conflicts': 0,
                'errors': 0,
            }
            
            # Perform sync based on direction
            if direction in ['pull', 'both']:
                pull_result = self._sync_direction(
                    shared_memory, local_memory, 'pull', dry_run
                )
                summary['pulled'] = pull_result['count']
                summary['conflicts'] += pull_result['conflicts']
                summary['errors'] += pull_result['errors']
            
            if direction in ['push', 'both']:
                push_result = self._sync_direction(
                    local_memory, shared_memory, 'push', dry_run
                )
                summary['pushed'] = push_result['count']
                summary['conflicts'] += push_result['conflicts']
                summary['errors'] += push_result['errors']
            
            # Update sync metadata
            if not dry_run:
                self._update_sync_metadata()
            
            return {
                'success': True,
                'summary': summary,
                'dry_run': dry_run,
            }
            
        except Exception as e:
            return {
                'success': False,
                'error': f"Sync failed: {e}"
            }
    
    def _sync_direction(
        self, 
        source_dir: Path, 
        target_dir: Path,
        direction: str,
        dry_run: bool
    ) -> Dict[str, Any]:
        """Sync files in one direction."""
        count = 0
        conflicts = 0
        errors = 0
        
        try:
            # Get exclude patterns
            exclude_patterns = self.config.get('sync.exclude_patterns', [])
            
            # Walk through source directory
            for source_file in source_dir.rglob("*"):
                if source_file.is_file():
                    # Check if file should be excluded
                    relative_path = source_file.relative_to(source_dir)
                    if self._should_exclude_file(relative_path, exclude_patterns):
                        continue
                    
                    target_file = target_dir / relative_path
                    
                    try:
                        result = self._sync_file(
                            source_file, target_file, direction, dry_run
                        )
                        
                        if result['synced']:
                            count += 1
                        if result['conflict']:
                            conflicts += 1
                            
                    except Exception as e:
                        errors += 1
                        print(f"Error syncing {source_file}: {e}")
            
            return {
                'count': count,
                'conflicts': conflicts, 
                'errors': errors,
            }
            
        except Exception:
            return {
                'count': count,
                'conflicts': conflicts,
                'errors': errors + 1,
            }
    
    def _sync_file(
        self,
        source_file: Path,
        target_file: Path,
        direction: str,
        dry_run: bool
    ) -> Dict[str, Any]:
        """Sync a single file."""
        synced = False
        conflict = False
        
        # Ensure target directory exists
        if not dry_run:
            ensure_directory_exists(target_file.parent)
        
        if not target_file.exists():
            # Simple copy - no conflict
            if not dry_run:
                shutil.copy2(source_file, target_file)
            synced = True
        else:
            # File exists - check for conflicts
            if not filecmp.cmp(source_file, target_file, shallow=False):
                # Files are different - handle conflict
                conflict_resolution = self.config.get('sync.conflict_resolution', 'prompt')
                
                if conflict_resolution == 'newest':
                    source_mtime = source_file.stat().st_mtime
                    target_mtime = target_file.stat().st_mtime
                    
                    if source_mtime > target_mtime:
                        if not dry_run:
                            self._backup_file(target_file)
                            shutil.copy2(source_file, target_file)
                        synced = True
                    else:
                        # Target is newer, don't sync
                        pass
                        
                elif conflict_resolution == 'shared':
                    if direction == 'pull':  # Prefer shared version
                        if not dry_run:
                            self._backup_file(target_file)
                            shutil.copy2(source_file, target_file)
                        synced = True
                        
                elif conflict_resolution == 'local':
                    if direction == 'push':  # Prefer local version
                        if not dry_run:
                            self._backup_file(target_file)
                            shutil.copy2(source_file, target_file)
                        synced = True
                        
                else:  # 'prompt' or unknown
                    # For CLI, we'll mark as conflict and let user handle
                    conflict = True
        
        return {
            'synced': synced,
            'conflict': conflict,
        }
    
    def _should_exclude_file(self, relative_path: Path, exclude_patterns: List[str]) -> bool:
        """Check if file should be excluded from sync."""
        path_str = str(relative_path)
        
        for pattern in exclude_patterns:
            if pattern in path_str or relative_path.name == pattern:
                return True
            
            # Handle glob patterns (simplified)
            if pattern.startswith('*.'):
                extension = pattern[2:]
                if path_str.endswith(extension):
                    return True
        
        return False
    
    def _backup_file(self, file_path: Path) -> None:
        """Create a backup of the file before overwriting."""
        if not self.config.get('sync.backup_before_sync', True):
            return
        
        backup_dir = self.config.data_dir / "backups"
        ensure_directory_exists(backup_dir)
        
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_name = f"{file_path.stem}_{timestamp}{file_path.suffix}"
        backup_path = backup_dir / backup_name
        
        try:
            shutil.copy2(file_path, backup_path)
        except Exception:
            pass  # Backup failed, but continue with sync
    
    def _update_sync_metadata(self) -> None:
        """Update sync metadata file."""
        metadata = {
            'last_sync': datetime.now().isoformat(),
            'sync_count': self._get_sync_count() + 1,
        }
        
        try:
            import json
            with open(self.sync_metadata_file, 'w', encoding='utf-8') as f:
                json.dump(metadata, f, indent=2)
        except Exception:
            pass  # Metadata update failed, but sync was successful
    
    def _get_sync_count(self) -> int:
        """Get the current sync count."""
        try:
            import json
            if self.sync_metadata_file.exists():
                with open(self.sync_metadata_file, 'r', encoding='utf-8') as f:
                    metadata = json.load(f)
                    return metadata.get('sync_count', 0)
        except Exception:
            pass
        
        return 0
    
    def get_sync_status(self) -> Dict[str, Any]:
        """Get current synchronization status."""
        try:
            import json
            if self.sync_metadata_file.exists():
                with open(self.sync_metadata_file, 'r', encoding='utf-8') as f:
                    metadata = json.load(f)
                    
                last_sync = metadata.get('last_sync')
                if last_sync:
                    # Convert ISO format to readable format
                    sync_time = datetime.fromisoformat(last_sync)
                    last_sync = sync_time.strftime("%Y-%m-%d %H:%M:%S")
                
                return {
                    'last_sync': last_sync,
                    'sync_count': metadata.get('sync_count', 0),
                    'has_synced': True,
                }
        except Exception:
            pass
        
        return {
            'last_sync': None,
            'sync_count': 0,
            'has_synced': False,
        }
    
    def detect_conflicts(self) -> List[Dict[str, Any]]:
        """Detect sync conflicts that need manual resolution."""
        conflicts = []
        
        if not self.config.shared_dir or not self.config.shared_dir.exists():
            return conflicts
        
        local_memory = self.config.memory_dir
        shared_memory = self.config.shared_dir / "memory"
        
        if not local_memory.exists() or not shared_memory.exists():
            return conflicts
        
        # Check for conflicting files
        exclude_patterns = self.config.get('sync.exclude_patterns', [])
        
        for local_file in local_memory.rglob("*.md"):
            if local_file.is_file():
                relative_path = local_file.relative_to(local_memory)
                
                if self._should_exclude_file(relative_path, exclude_patterns):
                    continue
                
                shared_file = shared_memory / relative_path
                
                if shared_file.exists():
                    if not filecmp.cmp(local_file, shared_file, shallow=False):
                        # Files are different
                        local_mtime = local_file.stat().st_mtime
                        shared_mtime = shared_file.stat().st_mtime
                        
                        conflicts.append({
                            'file': str(relative_path),
                            'local_path': local_file,
                            'shared_path': shared_file,
                            'local_modified': datetime.fromtimestamp(local_mtime).isoformat(),
                            'shared_modified': datetime.fromtimestamp(shared_mtime).isoformat(),
                            'newer': 'local' if local_mtime > shared_mtime else 'shared',
                        })
        
        return conflicts
    
    def resolve_conflict(
        self, 
        conflict: Dict[str, Any], 
        resolution: str
    ) -> bool:
        """Resolve a specific conflict."""
        try:
            local_path = Path(conflict['local_path'])
            shared_path = Path(conflict['shared_path'])
            
            if resolution == 'use_local':
                self._backup_file(shared_path)
                shutil.copy2(local_path, shared_path)
            elif resolution == 'use_shared':
                self._backup_file(local_path)
                shutil.copy2(shared_path, local_path)
            elif resolution == 'use_newer':
                if conflict['newer'] == 'local':
                    self._backup_file(shared_path)
                    shutil.copy2(local_path, shared_path)
                else:
                    self._backup_file(local_path)
                    shutil.copy2(shared_path, local_path)
            else:
                return False
            
            return True
            
        except Exception:
            return False