"""
Workspace analysis and management with advanced file operations
"""
import os
from typing import Dict, List, Any, Union, Optional
from pathlib import Path
import git
import aiofiles
import chardet
from datetime import datetime
import shutil
import re
from dataclasses import dataclass
import asyncio

@dataclass
class FileInfo:
    path: Path
    content_type: str
    size: int
    last_modified: datetime
    is_binary: bool
    encoding: Optional[str] = None

class FileChangeType:
    CREATE = "create"
    MODIFY = "modify"
    DELETE = "delete"
    RENAME = "rename"

class WorkspaceManager:
    def __init__(self, workspace_path: str):
        self.workspace_path = Path(workspace_path)
        self.git_repo = None
        self._initialize_git()
        
    def _initialize_git(self):
        """Initialize git repo if it exists"""
        try:
            self.git_repo = git.Repo(self.workspace_path)
        except git.exc.InvalidGitRepositoryError:
            self.git_repo = None
            
    async def read_file(self, file_path: Union[str, Path], binary: bool = False) -> Union[str, bytes]:
        """Read file content with automatic encoding detection"""
        path = Path(file_path)
        if not path.is_absolute():
            path = self.workspace_path / path
            
        try:
            if binary:
                async with aiofiles.open(path, 'rb') as f:
                    return await f.read()
            else:
                # Try UTF-8 first
                try:
                    async with aiofiles.open(path, 'r', encoding='utf-8') as f:
                        return await f.read()
                except UnicodeDecodeError:
                    # If UTF-8 fails, try to detect encoding
                    async with aiofiles.open(path, 'rb') as f:
                        raw_content = await f.read()
                    encoding = chardet.detect(raw_content)['encoding']
                    return raw_content.decode(encoding)
                    
        except Exception as e:
            raise FileNotFoundError(f"Could not read file {path}: {str(e)}")
            
    async def write_file(self, 
                      file_path: Union[str, Path], 
                      content: Union[str, bytes],
                      mode: str = 'w',
                      encoding: str = 'utf-8') -> bool:
        """Write content to file with automatic backup"""
        path = Path(file_path)
        if not path.is_absolute():
            path = self.workspace_path / path
            
        # Create parent directories if they don't exist
        path.parent.mkdir(parents=True, exist_ok=True)
        
        # Create backup if file exists
        if path.exists():
            await self.create_backup(path)
            
        try:
            if isinstance(content, str) and 'b' not in mode:
                async with aiofiles.open(path, mode, encoding=encoding) as f:
                    await f.write(content)
            else:
                async with aiofiles.open(path, f"{mode}b") as f:
                    await f.write(content if isinstance(content, bytes) else content.encode())
            return True
            
        except Exception as e:
            # Try to restore from backup if write failed
            try:
                await self.restore_latest_backup(path)
            except:
                pass
            raise IOError(f"Failed to write to {path}: {str(e)}")
            
    async def create_backup(self, file_path: Union[str, Path]) -> str:
        """Create a backup of a file"""
        path = Path(file_path)
        if not path.is_absolute():
            path = self.workspace_path / path
            
        if not path.exists():
            raise FileNotFoundError(f"File not found: {path}")
            
        backup_dir = self.workspace_path / '.backups' / path.relative_to(self.workspace_path)
        backup_dir.parent.mkdir(parents=True, exist_ok=True)
        
        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        backup_path = backup_dir.with_name(f"{path.stem}.{timestamp}{path.suffix}")
        
        shutil.copy2(path, backup_path)
        return str(backup_path)
        
    async def restore_latest_backup(self, file_path: Union[str, Path]) -> bool:
        """Restore the most recent backup of a file"""
        path = Path(file_path)
        if not path.is_absolute():
            path = self.workspace_path / path
            
        backup_dir = self.workspace_path / '.backups' / path.relative_to(self.workspace_path)
        if not backup_dir.parent.exists():
            raise FileNotFoundError(f"No backups found for {path}")
            
        # Find the most recent backup
        backups = sorted(backup_dir.parent.glob(f"{path.stem}.*{path.suffix}"))
        if not backups:
            raise FileNotFoundError(f"No backups found for {path}")
            
        latest_backup = backups[-1]
        shutil.copy2(latest_backup, path)
        return True
            
    def get_context(self) -> Dict[str, Any]:
        """Get current workspace context"""
        context = {
            "files": self._get_files(),
            "git_status": self._get_git_status(),
            "dependencies": self._get_dependencies(),
            "structure": self._get_project_structure()
        }
        return context
        
    def _get_files(self) -> List[Path]:
        """Get all relevant files in workspace"""
        files = []
        for file_path in self.workspace_path.rglob("*"):
            if file_path.is_file() and not self._should_ignore(file_path):
                files.append(file_path)
        return files
        
    def _get_git_status(self) -> Dict[str, Any]:
        """Get git status if available"""
        if not self.git_repo:
            return {"available": False}
            
        return {
            "available": True,
            "branch": self.git_repo.active_branch.name,
            "modified": [item.a_path for item in self.git_repo.index.diff(None)],
            "untracked": self.git_repo.untracked_files
        }
        
    async def search_in_files(self, 
                          pattern: str,
                          file_pattern: str = "*",
                          ignore_case: bool = True) -> List[Dict[str, Any]]:
        """Search for pattern in files"""
        results = []
        flags = re.IGNORECASE if ignore_case else 0
        
        try:
            regex = re.compile(pattern, flags)
        except re.error:
            regex = re.compile(re.escape(pattern), flags)
            
        for file_path in self.workspace_path.rglob(file_pattern):
            if not file_path.is_file() or self._should_ignore(file_path):
                continue
                
            try:
                content = await self.read_file(file_path)
                matches = []
                
                for i, line in enumerate(content.splitlines(), 1):
                    if regex.search(line):
                        matches.append({
                            'line_number': i,
                            'line': line.strip(),
                            'column': regex.search(line).start() + 1
                        })
                        
                if matches:
                    results.append({
                        'file': str(file_path.relative_to(self.workspace_path)),
                        'matches': matches
                    })
                    
            except Exception:
                # Skip files that can't be read
                continue
                
        return results
        
    def get_file_info(self, file_path: Union[str, Path]) -> FileInfo:
        """Get detailed file information"""
        path = Path(file_path)
        if not path.is_absolute():
            path = self.workspace_path / path
            
        if not path.exists():
            raise FileNotFoundError(f"File not found: {path}")
            
        # Check if file is binary
        try:
            with open(path, 'rb') as f:
                is_binary = bool(b'\0' in f.read(1024))
        except Exception:
            is_binary = True
            
        # Get file stats
        stats = path.stat()
        
        # Get content type
        import mimetypes
        content_type, _ = mimetypes.guess_type(str(path))
        
        # Get encoding for text files
        encoding = None
        if not is_binary:
            try:
                with open(path, 'rb') as f:
                    raw_content = f.read(1024)
                encoding = chardet.detect(raw_content)['encoding']
            except Exception:
                encoding = 'utf-8'
                
        return FileInfo(
            path=path,
            content_type=content_type or 'application/octet-stream',
            size=stats.st_size,
            last_modified=datetime.fromtimestamp(stats.st_mtime),
            is_binary=is_binary,
            encoding=encoding
        )
        
    def _get_dependencies(self) -> Dict[str, List[str]]:
        """Get project dependencies"""
        deps = {}
        
        # Check for Python dependencies
        if (self.workspace_path / "requirements.txt").exists():
            with open(self.workspace_path / "requirements.txt") as f:
                deps["python"] = f.read().splitlines()
                
        # Check for Node.js dependencies
        if (self.workspace_path / "package.json").exists():
            import json
            with open(self.workspace_path / "package.json") as f:
                package_json = json.load(f)
                deps["node"] = list(package_json.get("dependencies", {}).keys())
                
        return deps
        
    def _get_project_structure(self) -> Dict[str, Any]:
        """Get project directory structure"""
        def create_tree(path: Path) -> Dict[str, Any]:
            if path.is_file():
                return str(path)
            return {
                item.name: create_tree(item)
                for item in path.iterdir()
                if not self._should_ignore(item)
            }
            
        return create_tree(self.workspace_path)
        
    def _should_ignore(self, path: Path) -> bool:
        """Check if path should be ignored"""
        ignore_patterns = [
            "__pycache__",
            ".git",
            "node_modules",
            ".env",
            "*.pyc",
            "*.pyo",
            "*.pyd",
            ".DS_Store"
        ]
        
        return any(
            path.match(pattern)
            for pattern in ignore_patterns
        )
