"""
MCAL Storage Layer

Provides persistence for unified graphs across sessions.
This enables cross-session reasoning preservation - the core value proposition.
"""

import json
import logging
import os
import tempfile
from pathlib import Path
from typing import Optional
from datetime import datetime, timezone

from .unified_extractor import UnifiedGraph


def _utc_now() -> datetime:
    """Return current UTC time (timezone-aware)."""
    return datetime.now(timezone.utc)


logger = logging.getLogger(__name__)


class MCALStorage:
    """
    Persistent storage for MCAL data structures.
    
    Stores:
    - Intent graphs per user (with versioning)
    - Decision trails per user
    - Session metadata
    
    File structure:
    ~/.mcal/
      users/
        {user_id}/
          intent_graph.json      # Legacy 3-Pillar
          decisions.json          # Legacy 3-Pillar
          unified_graph.json      # Unified Deep (Issue #25)
          sessions.json
    """
    
    def __init__(self, base_path: Optional[Path] = None):
        """
        Initialize storage.
        
        Args:
            base_path: Base directory for storage (default: ~/.mcal)
        """
        if base_path is None:
            base_path = Path.home() / ".mcal"
        
        self.base_path = Path(base_path)
        self.users_path = self.base_path / "users"
        self.users_path.mkdir(parents=True, exist_ok=True)
        logger.info(f"MCAL storage initialized at {self.base_path}")
    
    def _get_user_path(self, user_id: str) -> Path:
        """Get path for user's data directory."""
        user_path = self.users_path / user_id
        user_path.mkdir(parents=True, exist_ok=True)
        return user_path
    
    def _atomic_write_json(self, target_path: Path, data: dict) -> None:
        """
        Write JSON data atomically using write-to-temp-then-rename.
        
        Issue #95: Prevents data corruption if process crashes mid-write.
        On POSIX systems, os.replace() is atomic on the same filesystem.
        
        Args:
            target_path: Final destination path for the JSON file
            data: Dictionary to serialize as JSON
        """
        target_dir = target_path.parent
        fd, tmp_path = tempfile.mkstemp(dir=target_dir, suffix='.json.tmp')
        try:
            with os.fdopen(fd, 'w') as f:
                json.dump(data, f, indent=2)
                f.flush()
                os.fsync(f.fileno())
            os.replace(tmp_path, str(target_path))  # Atomic on same filesystem
        except BaseException:
            # Clean up temp file on any failure (including KeyboardInterrupt)
            try:
                os.unlink(tmp_path)
            except OSError:
                pass
            raise
    
    # =========================================================================
    # Unified Graph Persistence (Issue #25)
    # =========================================================================
    
    def save_unified_graph(self, user_id: str, graph: UnifiedGraph) -> None:
        """
        Save unified graph for user.
        
        Args:
            user_id: User identifier
            graph: UnifiedGraph to save
        """
        user_path = self._get_user_path(user_id)
        graph_path = user_path / "unified_graph.json"
        
        # Use existing to_dict() method
        data = graph.to_dict()
        data["_metadata"] = {
            "user_id": user_id,
            "saved_at": _utc_now().isoformat(),
            "node_count": len(graph.nodes),
            "edge_count": len(graph.edges),
            "version": "unified_deep_v1"
        }
        
        self._atomic_write_json(graph_path, data)
        
        logger.info(f"Saved unified graph for {user_id}: {len(graph.nodes)} nodes, {len(graph.edges)} edges")
    
    def load_unified_graph(self, user_id: str) -> Optional[UnifiedGraph]:
        """
        Load unified graph for user.
        
        Args:
            user_id: User identifier
            
        Returns:
            UnifiedGraph or None if not found
        """
        user_path = self._get_user_path(user_id)
        graph_path = user_path / "unified_graph.json"
        
        if not graph_path.exists():
            logger.debug(f"No existing unified graph for {user_id}")
            return None
        
        try:
            with open(graph_path, 'r') as f:
                data = json.load(f)
            
            # Use existing from_dict() method
            graph = UnifiedGraph.from_dict(data)
            logger.info(f"Loaded unified graph for {user_id}: {len(graph.nodes)} nodes")
            return graph
            
        except Exception as e:
            logger.error(f"Failed to load unified graph for {user_id}: {e}")
            return None
    
    def delete_unified_graph(self, user_id: str) -> bool:
        """
        Delete unified graph for user.
        
        Args:
            user_id: User identifier
            
        Returns:
            True if deleted, False if not found
        """
        user_path = self._get_user_path(user_id)
        graph_path = user_path / "unified_graph.json"
        
        if graph_path.exists():
            graph_path.unlink()
            logger.info(f"Deleted unified graph for {user_id}")
            return True
        return False
    
    # =========================================================================
    # Utility Methods
    # =========================================================================
    
    def clear_user_data(self, user_id: str) -> None:
        """Clear all stored data for a user."""
        import shutil
        user_path = self._get_user_path(user_id)
        if user_path.exists():
            shutil.rmtree(user_path)
            logger.info(f"Cleared all data for {user_id}")
    
    def list_users(self) -> list[str]:
        """List all users with stored data."""
        if not self.users_path.exists():
            return []
        return [d.name for d in self.users_path.iterdir() if d.is_dir()]
