"""
Session management for CARL Control Panel.
Handles per-session config overrides in .carl/sessions/
"""
import json
from pathlib import Path
from datetime import datetime


class SessionManager:
    """Manages per-session configuration overrides."""

    def __init__(self, carl_path: Path):
        """Initialize with path to .carl directory."""
        self.carl_path = Path(carl_path)
        self.sessions_path = self.carl_path / 'sessions'

    def ensure_sessions_dir(self) -> bool:
        """Create sessions directory if it doesn't exist."""
        try:
            self.sessions_path.mkdir(parents=True, exist_ok=True)
            return True
        except Exception:
            return False

    def list_sessions(self, manifest_toggles: dict = None) -> list[dict]:
        """
        List all active sessions with their configs.

        Args:
            manifest_toggles: Global manifest toggles for computing effective values

        Returns:
            List of session configs sorted by last_activity desc
        """
        if not self.sessions_path.exists():
            return []

        sessions = []
        for session_file in self.sessions_path.glob("*.json"):
            try:
                with open(session_file, 'r') as f:
                    config = json.load(f)

                # Add effective values (merged with global)
                if manifest_toggles:
                    config['effective'] = self._compute_effective(
                        config.get('overrides', {}),
                        manifest_toggles
                    )

                # Add filepath for UI display
                config['filepath'] = str(session_file)

                sessions.append(config)
            except (json.JSONDecodeError, IOError):
                continue

        # Sort by last_activity descending
        sessions.sort(
            key=lambda s: s.get('last_activity', ''),
            reverse=True
        )

        return sessions

    def get_session(self, uuid: str, manifest_toggles: dict = None) -> dict | None:
        """
        Get a specific session config by UUID.

        Args:
            uuid: The session UUID
            manifest_toggles: Global manifest toggles for computing effective values

        Returns:
            Session config dict or None if not found
        """
        if not uuid:
            return None

        session_file = self.sessions_path / f"{uuid}.json"
        if not session_file.exists():
            return None

        try:
            with open(session_file, 'r') as f:
                config = json.load(f)

            # Add effective values
            if manifest_toggles:
                config['effective'] = self._compute_effective(
                    config.get('overrides', {}),
                    manifest_toggles
                )

            # Add filepath for UI display
            config['filepath'] = str(session_file)

            return config
        except (json.JSONDecodeError, IOError):
            return None

    def update_session_overrides(self, uuid: str, overrides: dict) -> dict:
        """
        Update session-specific overrides.

        Args:
            uuid: The session UUID
            overrides: Dict of override values (None = inherit, True/False = override)

        Returns:
            {'success': bool, 'error': str | None}
        """
        if not uuid:
            return {'success': False, 'error': 'No UUID provided'}

        session_file = self.sessions_path / f"{uuid}.json"
        if not session_file.exists():
            return {'success': False, 'error': 'Session not found'}

        try:
            with open(session_file, 'r') as f:
                config = json.load(f)

            # Update overrides (merge, don't replace entirely)
            current_overrides = config.get('overrides', {})
            for key, value in overrides.items():
                # Validate key
                if self._is_valid_override_key(key):
                    current_overrides[key] = value

            config['overrides'] = current_overrides
            config['last_activity'] = datetime.now().isoformat()

            with open(session_file, 'w') as f:
                json.dump(config, f, indent=2)

            return {'success': True}
        except (json.JSONDecodeError, IOError) as e:
            return {'success': False, 'error': str(e)}

    def update_session_title(self, uuid: str, title: str) -> dict:
        """
        Update session title.

        Args:
            uuid: The session UUID
            title: New title for the session

        Returns:
            {'success': bool, 'error': str | None}
        """
        if not uuid:
            return {'success': False, 'error': 'No UUID provided'}

        session_file = self.sessions_path / f"{uuid}.json"
        if not session_file.exists():
            return {'success': False, 'error': 'Session not found'}

        try:
            with open(session_file, 'r') as f:
                config = json.load(f)

            config['title'] = title.strip() if title else None
            config['last_activity'] = datetime.now().isoformat()

            with open(session_file, 'w') as f:
                json.dump(config, f, indent=2)

            return {'success': True, 'title': config['title']}
        except (json.JSONDecodeError, IOError) as e:
            return {'success': False, 'error': str(e)}

    def delete_session(self, uuid: str) -> dict:
        """
        Delete a session config.

        Args:
            uuid: The session UUID

        Returns:
            {'success': bool, 'error': str | None}
        """
        if not uuid:
            return {'success': False, 'error': 'No UUID provided'}

        session_file = self.sessions_path / f"{uuid}.json"
        if not session_file.exists():
            return {'success': False, 'error': 'Session not found'}

        try:
            session_file.unlink()
            return {'success': True}
        except IOError as e:
            return {'success': False, 'error': str(e)}

    def _compute_effective(self, overrides: dict, manifest_toggles: dict) -> dict:
        """
        Compute effective values by merging overrides with global manifest.

        Args:
            overrides: Session-specific overrides (None = inherit)
            manifest_toggles: Global manifest values

        Returns:
            Dict of effective values for each toggle
        """
        effective = {}

        # Collect all keys from both sources
        all_keys = set()

        # Add special keys
        special_keys = {'DEVMODE', 'CLAUDEMD_REINJECT'}
        all_keys.update(special_keys)

        # Add any _STATE keys from manifest
        for key in manifest_toggles:
            if key.endswith('_STATE') or key in special_keys:
                all_keys.add(key)

        # Add any _STATE keys from overrides
        for key in overrides:
            if key.endswith('_STATE') or key in special_keys:
                all_keys.add(key)

        # Compute effective value for each key
        for key in all_keys:
            override_value = overrides.get(key)
            if override_value is not None:
                # Session override takes precedence
                effective[key] = override_value
            else:
                # Inherit from manifest (default to False if not set)
                effective[key] = manifest_toggles.get(key, False)

        return effective

    def _is_valid_override_key(self, key: str) -> bool:
        """Check if a key is a valid override key."""
        # Special keys (not domain-based)
        special_keys = {'DEVMODE', 'CLAUDEMD_REINJECT'}
        if key in special_keys:
            return True
        # Accept any {DOMAIN}_STATE key dynamically
        if key.endswith('_STATE'):
            return True
        return False


def get_available_toggles(workspace_path: Path) -> list[dict]:
    """
    Get list of available toggles for session overrides.

    Dynamically reads from .carl/ to show only domains with actual rule files.

    Args:
        workspace_path: Path to the workspace root

    Returns:
        List of toggle definitions with keys: key, label, type, requires_hook (optional)
    """
    from . import hooks

    # Base toggles - always available
    toggles = [
        {'key': 'DEVMODE', 'label': 'Dev Mode', 'type': 'boolean', 'description': 'Shows debug output: domains loaded, rules applied, governance'},
    ]

    # Special domain descriptions (for domains without recall keywords)
    special_descriptions = {
        'CONTEXT': 'Session-specific context injected at start',
        'COMMANDS': 'User-defined star-commands (*command syntax)',
    }

    # Find .carl folder
    carl_path = workspace_path / '.carl'
    if not carl_path.exists():
        return toggles

    # Load manifest to get domain metadata (recall keywords, etc.)
    manifest_path = carl_path / 'manifest'
    manifest_data = {}
    if manifest_path.exists():
        try:
            with open(manifest_path, 'r') as f:
                for line in f:
                    line = line.strip()
                    if '=' in line and not line.startswith('#'):
                        key, value = line.split('=', 1)
                        manifest_data[key.strip()] = value.strip()
        except Exception:
            pass

    # Get domain files that exist (exclude only manifest and sessions folder)
    excluded_files = {'manifest', 'sessions'}
    domain_files = set()
    for f in carl_path.iterdir():
        if f.is_file() and f.name.lower() not in excluded_files and not f.name.startswith('.'):
            domain_files.add(f.name.upper())

    # Add toggle for each domain file that exists
    for domain in sorted(domain_files):
        # Get recall keywords and exclusions from manifest if available
        recall = manifest_data.get(f'{domain}_RECALL', '')
        exclude = manifest_data.get(f'{domain}_EXCLUDE', '')
        # Use special description if no recall keywords
        description = special_descriptions.get(domain, f'Enable {domain.lower()} rules')
        toggles.append({
            'key': f'{domain}_STATE',
            'label': f'{domain} Domain',
            'type': 'boolean',
            'description': description,
            'recall': recall,
            'exclude': exclude
        })

    # Check which optional hooks are wired (active) - legacy system
    optional_result = hooks.check_optional_hooks_status(workspace_path)

    # Add toggles for wired addon hooks (legacy)
    if optional_result.get('success'):
        for hook in optional_result.get('hooks', []):
            # Only include wired hooks in session toggles
            if hook.get('status') == 'wired':
                manifest_key = hook.get('manifest_key')
                if manifest_key:
                    toggles.append({
                        'key': manifest_key,
                        'label': hook.get('name', hook.get('filename', '')),
                        'type': 'addon',
                        'description': hook.get('description', ''),
                        'hook_type': hook.get('hook_type', 'UserPromptSubmit')
                    })

    # Add toggles for installed resources (new modular system)
    try:
        from . import resources as resources_api
        resource_toggles = resources_api.get_installed_hook_resources(str(carl_path))
        for resource in resource_toggles:
            manifest_key = resource.get('manifest_key')
            if manifest_key:
                # Avoid duplicates (in case same hook exists in both systems)
                existing_keys = {t['key'] for t in toggles}
                if f'{manifest_key}_GLOBAL' not in existing_keys and manifest_key not in existing_keys:
                    toggles.append({
                        'key': f'{manifest_key}_GLOBAL',
                        'label': resource.get('name', ''),
                        'type': 'addon',
                        'description': resource.get('description', ''),
                        'hook_type': 'Resource Hook'
                    })
    except Exception:
        pass  # Resources API not available or error

    return toggles
