"""
Workspace management functionality for Issue #144.

This module provides workspace templates, multi-project support, and
collaborative workspace features.
"""

import json
import yaml
import shutil
import zipfile
import tempfile
from pathlib import Path
from typing import Dict, Any, List, Optional
from dataclasses import dataclass, field
from datetime import datetime

from markitect.assets import AssetManager


@dataclass
class TemplateMetadata:
    """Metadata for workspace templates."""
    name: str
    description: str
    version: str
    created_at: datetime
    asset_count: int
    author: str = "Unknown"
    tags: List[str] = field(default_factory=list)


@dataclass
class TemplateResult:
    """Result of template creation."""
    success: bool
    template_path: Path
    template_name: str
    error: Optional[Exception] = None


@dataclass
class WorkspaceCreationResult:
    """Result of workspace creation from template."""
    success: bool
    workspace_path: Path
    project_name: str
    error: Optional[Exception] = None


@dataclass
class ProjectResult:
    """Result of project operations."""
    success: bool
    project_path: Path
    project_name: str
    error: Optional[Exception] = None


@dataclass
class SyncResult:
    """Result of workspace synchronization."""
    synchronized_count: int
    skipped_count: int
    error_count: int
    errors: List[Exception] = field(default_factory=list)


@dataclass
class BackupResult:
    """Result of workspace backup."""
    success: bool
    backup_path: Path
    backup_size: int
    error: Optional[Exception] = None


@dataclass
class RestoreResult:
    """Result of workspace restore."""
    success: bool
    restored_path: Path
    files_restored: int
    error: Optional[Exception] = None


@dataclass
class WorkspaceState:
    """Snapshot of workspace state."""
    timestamp: datetime
    file_checksums: Dict[str, str]
    directory_structure: List[str]
    asset_hashes: List[str]


@dataclass
class ConflictInfo:
    """Information about a workspace conflict."""
    file_path: Path
    conflict_type: str
    local_timestamp: datetime
    remote_timestamp: datetime


@dataclass
class MergeResult:
    """Result of conflict resolution."""
    resolved_conflicts: int
    unresolved_conflicts: int
    merge_strategy: str


class WorkspaceTemplate:
    """Workspace template management."""

    def __init__(self, template_path: Path):
        """Initialize workspace template."""
        self.template_path = template_path
        self.metadata_file = template_path / "template.json"

    def get_metadata(self) -> TemplateMetadata:
        """Get template metadata."""
        if self.metadata_file.exists():
            metadata_dict = json.loads(self.metadata_file.read_text())
            return TemplateMetadata(**metadata_dict)
        else:
            return TemplateMetadata(
                name="Unknown",
                description="No description",
                version="1.0.0",
                created_at=datetime.now(),
                asset_count=0
            )


class WorkspaceManager:
    """Workspace management system."""

    def __init__(self, templates_dir: Optional[Path] = None):
        """Initialize workspace manager."""
        self.templates_dir = templates_dir or Path.home() / ".markitect" / "templates"
        self.templates_dir.mkdir(parents=True, exist_ok=True)

    def create_template(self, name: str, source_path: Path, description: str = "",
                       include_assets: bool = True, configuration: Optional[Dict] = None) -> TemplateResult:
        """Create a workspace template from existing workspace."""
        try:
            template_path = self.templates_dir / name
            template_path.mkdir(exist_ok=True)

            # Copy workspace structure
            self._copy_workspace_structure(source_path, template_path, include_assets)

            # Count assets
            asset_count = 0
            if include_assets and (source_path / "assets").exists():
                asset_count = len(list((source_path / "assets").rglob("*")))

            # Create template metadata
            metadata = {
                "name": name,
                "description": description,
                "version": "1.0.0",
                "created_at": datetime.now().isoformat(),
                "asset_count": asset_count,
                "author": "Unknown",
                "tags": []
            }

            metadata_file = template_path / "template.json"
            metadata_file.write_text(json.dumps(metadata, indent=2))

            # Save configuration if provided
            if configuration:
                config_file = template_path / "markitect.yaml"
                config_file.write_text(yaml.dump(configuration, indent=2))

            return TemplateResult(
                success=True,
                template_path=template_path,
                template_name=name
            )

        except Exception as e:
            return TemplateResult(
                success=False,
                template_path=Path(),
                template_name=name,
                error=e
            )

    def get_template_metadata(self, template_name: str) -> TemplateMetadata:
        """Get metadata for a specific template."""
        template_path = self.templates_dir / template_name
        template = WorkspaceTemplate(template_path)
        return template.get_metadata()

    def create_workspace_from_template(self, template_name: str, target_path: Path,
                                     project_name: str) -> WorkspaceCreationResult:
        """Create a new workspace from a template."""
        try:
            template_path = self.templates_dir / template_name

            if not template_path.exists():
                raise FileNotFoundError(f"Template '{template_name}' not found")

            # Create target directory
            target_path.mkdir(parents=True, exist_ok=True)

            # Copy template contents
            self._copy_workspace_structure(template_path, target_path, include_assets=True)

            # Update project-specific files
            self._customize_workspace(target_path, project_name)

            return WorkspaceCreationResult(
                success=True,
                workspace_path=target_path,
                project_name=project_name
            )

        except Exception as e:
            return WorkspaceCreationResult(
                success=False,
                workspace_path=target_path,
                project_name=project_name,
                error=e
            )

    def initialize_multi_project_workspace(self, workspace_root: Path):
        """Initialize a multi-project workspace."""
        workspace_root.mkdir(parents=True, exist_ok=True)

        # Create shared directories
        (workspace_root / "shared_assets").mkdir(exist_ok=True)
        (workspace_root / "templates").mkdir(exist_ok=True)
        (workspace_root / "config").mkdir(exist_ok=True)

        # Create workspace configuration
        config = {
            "workspace_type": "multi_project",
            "shared_assets_enabled": True,
            "project_isolation": True,
            "created_at": datetime.now().isoformat()
        }

        config_file = workspace_root / "workspace.yaml"
        config_file.write_text(yaml.dump(config, indent=2))

    def add_project(self, workspace_root: Path, project_name: str,
                   template: Optional[str] = None) -> ProjectResult:
        """Add a project to multi-project workspace."""
        try:
            project_path = workspace_root / project_name
            project_path.mkdir(exist_ok=True)

            if template:
                # Use template if specified
                result = self.create_workspace_from_template(template, project_path, project_name)
                if not result.success:
                    raise result.error or Exception("Template creation failed")
            else:
                # Create basic project structure
                (project_path / "docs").mkdir(exist_ok=True)
                (project_path / "assets").mkdir(exist_ok=True)

            return ProjectResult(
                success=True,
                project_path=project_path,
                project_name=project_name
            )

        except Exception as e:
            return ProjectResult(
                success=False,
                project_path=workspace_root / project_name,
                project_name=project_name,
                error=e
            )

    def get_shared_asset_library(self, workspace_root: Path) -> Optional[AssetManager]:
        """Get shared asset library for multi-project workspace."""
        shared_assets_path = workspace_root / "shared_assets"
        if shared_assets_path.exists():
            return AssetManager(storage_path=shared_assets_path)
        return None

    def initialize_workspace(self, workspace_path: Path):
        """Initialize a single workspace."""
        workspace_path.mkdir(parents=True, exist_ok=True)
        (workspace_path / "assets").mkdir(exist_ok=True)
        (workspace_path / "docs").mkdir(exist_ok=True)

    def synchronize_assets(self, source_workspace: Path, target_workspace: Path,
                          sync_mode: str = "incremental") -> SyncResult:
        """Synchronize assets between workspaces."""
        result = SyncResult(
            synchronized_count=0,
            skipped_count=0,
            error_count=0
        )

        try:
            source_assets = source_workspace / "assets"
            target_assets = target_workspace / "assets"

            if not source_assets.exists():
                return result

            target_assets.mkdir(exist_ok=True)

            # Simple synchronization (copy new files)
            for asset_file in source_assets.rglob("*"):
                if asset_file.is_file():
                    relative_path = asset_file.relative_to(source_assets)
                    target_file = target_assets / relative_path

                    if not target_file.exists() or sync_mode == "overwrite":
                        target_file.parent.mkdir(parents=True, exist_ok=True)
                        shutil.copy2(asset_file, target_file)
                        result.synchronized_count += 1
                    else:
                        result.skipped_count += 1

        except Exception as e:
            result.error_count += 1
            result.errors.append(e)

        return result

    def create_backup(self, workspace_path: Path, backup_path: Path,
                     include_assets: bool = True, compression_level: int = 6) -> BackupResult:
        """Create a backup of workspace."""
        try:
            with zipfile.ZipFile(backup_path, 'w', zipfile.ZIP_DEFLATED, compresslevel=compression_level) as backup_zip:
                for file_path in workspace_path.rglob("*"):
                    if file_path.is_file():
                        # Skip assets if not included
                        if not include_assets and "assets" in file_path.parts:
                            continue

                        arc_name = file_path.relative_to(workspace_path)
                        backup_zip.write(file_path, arc_name)

            backup_size = backup_path.stat().st_size

            return BackupResult(
                success=True,
                backup_path=backup_path,
                backup_size=backup_size
            )

        except Exception as e:
            return BackupResult(
                success=False,
                backup_path=backup_path,
                backup_size=0,
                error=e
            )

    def restore_from_backup(self, backup_path: Path, target_path: Path) -> RestoreResult:
        """Restore workspace from backup."""
        try:
            target_path.mkdir(parents=True, exist_ok=True)

            files_restored = 0
            with zipfile.ZipFile(backup_path, 'r') as backup_zip:
                backup_zip.extractall(target_path)
                files_restored = len(backup_zip.namelist())

            return RestoreResult(
                success=True,
                restored_path=target_path,
                files_restored=files_restored
            )

        except Exception as e:
            return RestoreResult(
                success=False,
                restored_path=target_path,
                files_restored=0,
                error=e
            )

    def capture_workspace_state(self, workspace_path: Path) -> WorkspaceState:
        """Capture current state of workspace."""
        import hashlib

        file_checksums = {}
        directory_structure = []
        asset_hashes = []

        for item_path in workspace_path.rglob("*"):
            relative_path = str(item_path.relative_to(workspace_path))

            if item_path.is_file():
                # Calculate file checksum
                content = item_path.read_bytes()
                checksum = hashlib.md5(content).hexdigest()
                file_checksums[relative_path] = checksum

                # Track asset hashes
                if "assets" in item_path.parts:
                    asset_hashes.append(checksum)

            directory_structure.append(relative_path)

        return WorkspaceState(
            timestamp=datetime.now(),
            file_checksums=file_checksums,
            directory_structure=directory_structure,
            asset_hashes=asset_hashes
        )

    def detect_conflicts(self, state1: WorkspaceState, state2: WorkspaceState) -> List[ConflictInfo]:
        """Detect conflicts between workspace states."""
        conflicts = []

        # Find files that exist in both states but have different checksums
        for file_path, checksum1 in state1.file_checksums.items():
            if file_path in state2.file_checksums:
                checksum2 = state2.file_checksums[file_path]
                if checksum1 != checksum2:
                    conflict = ConflictInfo(
                        file_path=Path(file_path),
                        conflict_type="content_conflict",
                        local_timestamp=state1.timestamp,
                        remote_timestamp=state2.timestamp
                    )
                    conflicts.append(conflict)

        return conflicts

    def resolve_conflicts(self, conflicts: List[ConflictInfo],
                         resolution_strategy: str = "manual") -> MergeResult:
        """Resolve workspace conflicts."""
        # Mock conflict resolution
        result = MergeResult(
            resolved_conflicts=len(conflicts),
            unresolved_conflicts=0,
            merge_strategy=resolution_strategy
        )

        return result

    def _copy_workspace_structure(self, source: Path, target: Path, include_assets: bool):
        """Copy workspace structure from source to target."""
        for item in source.rglob("*"):
            if item.is_file():
                relative_path = item.relative_to(source)

                # Skip assets if not included
                if not include_assets and "assets" in relative_path.parts:
                    continue

                # Skip template metadata
                if item.name == "template.json":
                    continue

                target_path = target / relative_path
                target_path.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy2(item, target_path)

    def _customize_workspace(self, workspace_path: Path, project_name: str):
        """Customize workspace for specific project."""
        # Update any configuration files with project name
        config_files = list(workspace_path.glob("*.yaml")) + list(workspace_path.glob("*.yml"))

        for config_file in config_files:
            try:
                content = config_file.read_text()
                # Replace placeholder project names
                content = content.replace("{{PROJECT_NAME}}", project_name)
                content = content.replace("New Project", project_name)
                config_file.write_text(content)
            except Exception:
                pass  # Ignore errors in customization