"""
Project Context Manager

Provides cached, project-isolated context for Claude MCP sessions.
Loads all relevant project data (components, tokens, config, health, etc.)
and caches it for performance.
"""

import json
import asyncio
from datetime import datetime, timedelta
from dataclasses import dataclass, asdict
from typing import Dict, Any, Optional, List
from pathlib import Path

# Import from existing DSS modules
import sys
sys.path.insert(0, str(Path(__file__).parent.parent.parent))

from storage.json_store import Projects, Components, Tokens
from analyze.scanner import ProjectScanner
from ..config import mcp_config


@dataclass
class ProjectContext:
    """Complete project context for MCP sessions"""

    project_id: str
    name: str
    description: Optional[str]
    path: Optional[Path]

    # Component data
    components: List[Dict[str, Any]]
    component_count: int

    # Token/Style data
    tokens: Dict[str, Any]
    styles: List[Dict[str, Any]]

    # Project configuration
    config: Dict[str, Any]

    # User's enabled integrations (user-scoped)
    integrations: Dict[str, Any]

    # Project health & metrics
    health: Dict[str, Any]
    stats: Dict[str, Any]

    # Discovery/scan results
    discovery: Dict[str, Any]

    # Metadata
    loaded_at: datetime
    cache_expires_at: datetime

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for JSON serialization"""
        data = asdict(self)
        data['loaded_at'] = self.loaded_at.isoformat()
        data['cache_expires_at'] = self.cache_expires_at.isoformat()
        if self.path:
            data['path'] = str(self.path)
        return data

    def is_expired(self) -> bool:
        """Check if cache has expired"""
        return datetime.now() >= self.cache_expires_at


class ProjectContextManager:
    """
    Manages project contexts with TTL-based caching.

    Provides fast access to project data for MCP tools while ensuring
    data freshness and project isolation.
    """

    def __init__(self):
        self._cache: Dict[str, ProjectContext] = {}
        self._cache_ttl = timedelta(seconds=mcp_config.CONTEXT_CACHE_TTL)

    async def get_context(
        self,
        project_id: str,
        user_id: Optional[int] = None,
        force_refresh: bool = False
    ) -> Optional[ProjectContext]:
        """
        Get project context, using cache if available.

        Args:
            project_id: Project ID
            user_id: User ID for loading user-scoped integrations
            force_refresh: Force cache refresh

        Returns:
            ProjectContext or None if project not found
        """
        # Check cache first
        cache_key = f"{project_id}:{user_id or 'anonymous'}"
        if not force_refresh and cache_key in self._cache:
            ctx = self._cache[cache_key]
            if not ctx.is_expired():
                return ctx

        # Load fresh context
        context = await self._load_context(project_id, user_id)
        if context:
            self._cache[cache_key] = context

        return context

    async def _load_context(
        self,
        project_id: str,
        user_id: Optional[int] = None
    ) -> Optional[ProjectContext]:
        """Load complete project context from database and filesystem"""

        # Run database queries in thread pool to avoid blocking
        loop = asyncio.get_event_loop()

        # Load project metadata
        project = await loop.run_in_executor(None, self._load_project, project_id)
        if not project:
            return None

        # Load components, styles, stats in parallel
        components_task = loop.run_in_executor(None, self._load_components, project_id)
        styles_task = loop.run_in_executor(None, self._load_styles, project_id)
        stats_task = loop.run_in_executor(None, self._load_stats, project_id)
        integrations_task = loop.run_in_executor(None, self._load_integrations, project_id, user_id)

        components = await components_task
        styles = await styles_task
        stats = await stats_task
        integrations = await integrations_task

        # Load tokens from filesystem if project has a path
        tokens = {}
        project_path = None
        if project.get('figma_file_key'):
            # Try to find project path based on naming convention
            # (This can be enhanced based on actual project structure)
            project_path = Path.cwd()
            tokens = await loop.run_in_executor(None, self._load_tokens, project_path)

        # Load discovery/scan data
        discovery = await loop.run_in_executor(None, self._load_discovery, project_path)

        # Compute health score
        health = self._compute_health(components, tokens, stats)

        # Build context
        now = datetime.now()
        context = ProjectContext(
            project_id=project_id,
            name=project['name'],
            description=project.get('description'),
            path=project_path,
            components=components,
            component_count=len(components),
            tokens=tokens,
            styles=styles,
            config={
                'figma_file_key': project.get('figma_file_key'),
                'status': project.get('status', 'active')
            },
            integrations=integrations,
            health=health,
            stats=stats,
            discovery=discovery,
            loaded_at=now,
            cache_expires_at=now + self._cache_ttl
        )

        return context

    def _load_project(self, project_id: str) -> Optional[Dict[str, Any]]:
        """Load project metadata from database"""
        try:
            with get_connection() as conn:
                row = conn.execute(
                    "SELECT * FROM projects WHERE id = ?",
                    (project_id,)
                ).fetchone()

                if row:
                    return dict(row)
                return None
        except Exception as e:
            print(f"Error loading project: {e}")
            return None

    def _load_components(self, project_id: str) -> List[Dict[str, Any]]:
        """Load all components for project"""
        try:
            with get_connection() as conn:
                rows = conn.execute(
                    """
                    SELECT id, name, figma_key, description,
                           properties, variants, code_generated,
                           created_at, updated_at
                    FROM components
                    WHERE project_id = ?
                    ORDER BY name
                    """,
                    (project_id,)
                ).fetchall()

                components = []
                for row in rows:
                    comp = dict(row)
                    # Parse JSON fields
                    if comp.get('properties'):
                        comp['properties'] = json.loads(comp['properties'])
                    if comp.get('variants'):
                        comp['variants'] = json.loads(comp['variants'])
                    components.append(comp)

                return components
        except Exception as e:
            print(f"Error loading components: {e}")
            return []

    def _load_styles(self, project_id: str) -> List[Dict[str, Any]]:
        """Load all styles for project"""
        try:
            with get_connection() as conn:
                rows = conn.execute(
                    """
                    SELECT id, name, type, figma_key, properties, created_at
                    FROM styles
                    WHERE project_id = ?
                    ORDER BY type, name
                    """,
                    (project_id,)
                ).fetchall()

                styles = []
                for row in rows:
                    style = dict(row)
                    if style.get('properties'):
                        style['properties'] = json.loads(style['properties'])
                    styles.append(style)

                return styles
        except Exception as e:
            print(f"Error loading styles: {e}")
            return []

    def _load_stats(self, project_id: str) -> Dict[str, Any]:
        """Load project statistics"""
        try:
            with get_connection() as conn:
                # Component count by type
                component_stats = conn.execute(
                    """
                    SELECT COUNT(*) as total,
                           SUM(CASE WHEN code_generated = 1 THEN 1 ELSE 0 END) as generated
                    FROM components
                    WHERE project_id = ?
                    """,
                    (project_id,)
                ).fetchone()

                # Style count by type
                style_stats = conn.execute(
                    """
                    SELECT type, COUNT(*) as count
                    FROM styles
                    WHERE project_id = ?
                    GROUP BY type
                    """,
                    (project_id,)
                ).fetchall()

                return {
                    'components': dict(component_stats) if component_stats else {'total': 0, 'generated': 0},
                    'styles': {row['type']: row['count'] for row in style_stats}
                }
        except Exception as e:
            print(f"Error loading stats: {e}")
            return {'components': {'total': 0, 'generated': 0}, 'styles': {}}

    def _load_integrations(self, project_id: str, user_id: Optional[int]) -> Dict[str, Any]:
        """Load user's enabled integrations for this project"""
        if not user_id:
            return {}

        try:
            with get_connection() as conn:
                rows = conn.execute(
                    """
                    SELECT integration_type, config, enabled, last_used_at
                    FROM project_integrations
                    WHERE project_id = ? AND user_id = ? AND enabled = 1
                    """,
                    (project_id, user_id)
                ).fetchall()

                # Return decrypted config for each integration
                integrations = {}
                cipher = mcp_config.get_cipher()

                for row in rows:
                    integration_type = row['integration_type']
                    encrypted_config = row['config']

                    # Decrypt config
                    if cipher:
                        try:
                            decrypted_config = cipher.decrypt(encrypted_config.encode()).decode()
                            config = json.loads(decrypted_config)
                        except Exception as e:
                            print(f"Error decrypting integration config: {e}")
                            config = {}
                    else:
                        # No encryption key, try to parse as JSON
                        try:
                            config = json.loads(encrypted_config)
                        except:
                            config = {}

                    integrations[integration_type] = {
                        'enabled': True,
                        'config': config,
                        'last_used_at': row['last_used_at']
                    }

                return integrations
        except Exception as e:
            print(f"Error loading integrations: {e}")
            return {}

    def _load_tokens(self, project_path: Optional[Path]) -> Dict[str, Any]:
        """Load design tokens from filesystem"""
        if not project_path:
            return {}

        tokens = {}
        token_files = ['tokens.json', 'design-tokens.json', 'variables.json']

        for token_file in token_files:
            token_path = project_path / token_file
            if token_path.exists():
                try:
                    with open(token_path) as f:
                        tokens = json.load(f)
                    break
                except Exception as e:
                    print(f"Error loading tokens from {token_path}: {e}")

        return tokens

    def _load_discovery(self, project_path: Optional[Path]) -> Dict[str, Any]:
        """Load project discovery data"""
        if not project_path:
            return {}

        try:
            scanner = ProjectScanner(str(project_path))
            discovery = scanner.scan()
            return discovery
        except Exception as e:
            print(f"Error running discovery scan: {e}")
            return {}

    def _compute_health(
        self,
        components: List[Dict],
        tokens: Dict,
        stats: Dict
    ) -> Dict[str, Any]:
        """Compute project health score"""
        score = 100
        issues = []

        # Deduct points for missing components
        if stats['components']['total'] == 0:
            score -= 30
            issues.append("No components defined")

        # Deduct points for no tokens
        if not tokens:
            score -= 20
            issues.append("No design tokens defined")

        # Deduct points for ungeneratedcomponents
        total = stats['components']['total']
        generated = stats['components']['generated']
        if total > 0 and generated < total:
            percentage = (generated / total) * 100
            if percentage < 50:
                score -= 20
                issues.append(f"Low code generation: {percentage:.1f}%")
            elif percentage < 80:
                score -= 10
                issues.append(f"Medium code generation: {percentage:.1f}%")

        # Compute grade
        if score >= 90:
            grade = 'A'
        elif score >= 80:
            grade = 'B'
        elif score >= 70:
            grade = 'C'
        elif score >= 60:
            grade = 'D'
        else:
            grade = 'F'

        return {
            'score': max(0, score),
            'grade': grade,
            'issues': issues
        }

    def clear_cache(self, project_id: Optional[str] = None):
        """Clear cache for specific project or all projects"""
        if project_id:
            # Clear all cache entries for this project
            keys_to_remove = [k for k in self._cache.keys() if k.startswith(f"{project_id}:")]
            for key in keys_to_remove:
                del self._cache[key]
        else:
            # Clear all cache
            self._cache.clear()


# Singleton instance
_context_manager = None


def get_context_manager() -> ProjectContextManager:
    """Get singleton context manager instance"""
    global _context_manager
    if _context_manager is None:
        _context_manager = ProjectContextManager()
    return _context_manager
