#!/usr/bin/env python3
"""
Settings Manager for Media Downloader
Handles settings storage in database with JSON file compatibility
"""

import json
import sqlite3
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Optional, Any, Union, Tuple
from contextlib import contextmanager
import threading
from modules.universal_logger import get_logger

logger = get_logger('SettingsManager')


class SettingsManager:
    """Manage application settings in database (thread-safe)"""

    def __init__(self, db_path: str):
        """
        Initialize settings manager

        Args:
            db_path: Path to SQLite database
        """
        self.db_path = db_path
        self._write_lock = threading.RLock()  # Reentrant lock for write operations
        self._create_tables()

    @contextmanager
    def _get_connection(self, for_write: bool = False):
        """Get database connection (thread-safe)"""
        conn = sqlite3.connect(self.db_path, timeout=30.0, check_same_thread=False)
        conn.row_factory = sqlite3.Row
        try:
            if for_write:
                with self._write_lock:
                    yield conn
            else:
                yield conn
        finally:
            conn.close()

    def _create_tables(self):
        """Create settings table if it doesn't exist"""
        with self._get_connection(for_write=True) as conn:
            cursor = conn.cursor()

            cursor.execute('''
                CREATE TABLE IF NOT EXISTS settings (
                    key TEXT PRIMARY KEY,
                    value TEXT NOT NULL,
                    value_type TEXT NOT NULL,
                    category TEXT,
                    description TEXT,
                    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
                    updated_by TEXT DEFAULT 'system'
                )
            ''')

            # Create index for category lookups
            cursor.execute('''
                CREATE INDEX IF NOT EXISTS idx_settings_category
                ON settings(category)
            ''')

            conn.commit()
            logger.info("Settings tables initialized")

    def get(self, key: str, default: Any = None) -> Any:
        """
        Get a setting value

        Args:
            key: Setting key (supports dot notation, e.g., 'instagram.enabled')
            default: Default value if not found

        Returns:
            Setting value or default
        """
        with self._get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute('SELECT value, value_type FROM settings WHERE key = ?', (key,))
            row = cursor.fetchone()

            if not row:
                return default

            value, value_type = row['value'], row['value_type']
            return self._deserialize_value(value, value_type)

    def set(self, key: str, value: Any, category: str = None,
            description: str = None, updated_by: str = 'system'):
        """
        Set a setting value

        Args:
            key: Setting key
            value: Setting value (will be serialized to JSON if needed)
            category: Optional category
            description: Optional description
            updated_by: Who updated the setting
        """
        value_str, value_type = self._serialize_value(value)

        with self._get_connection(for_write=True) as conn:
            cursor = conn.cursor()
            cursor.execute('''
                INSERT OR REPLACE INTO settings
                (key, value, value_type, category, description, updated_at, updated_by)
                VALUES (?, ?, ?, ?, ?, ?, ?)
            ''', (key, value_str, value_type, category, description,
                  datetime.now().isoformat(), updated_by))
            conn.commit()
            logger.debug(f"Setting updated: {key} = {value_str[:100]}")

    def get_category(self, category: str) -> Dict[str, Any]:
        """
        Get all settings in a category

        Args:
            category: Category name

        Returns:
            Dictionary of settings
        """
        with self._get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute('''
                SELECT key, value, value_type
                FROM settings
                WHERE category = ?
            ''', (category,))

            result = {}
            for row in cursor.fetchall():
                key = row['key']
                value = self._deserialize_value(row['value'], row['value_type'])
                result[key] = value

            return result

    def get_all(self) -> Dict[str, Any]:
        """
        Get all settings as a nested dictionary

        Returns:
            Nested dictionary of all settings
        """
        with self._get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute('SELECT key, value, value_type FROM settings')

            result = {}
            for row in cursor.fetchall():
                key = row['key']
                value = self._deserialize_value(row['value'], row['value_type'])

                # Support nested keys like 'instagram.enabled'
                self._set_nested(result, key, value)

            return result

    def delete(self, key: str):
        """Delete a setting"""
        with self._get_connection(for_write=True) as conn:
            cursor = conn.cursor()
            cursor.execute('DELETE FROM settings WHERE key = ?', (key,))
            conn.commit()
            logger.debug(f"Setting deleted: {key}")

    def migrate_from_json(self, json_path: str):
        """
        Migrate settings from JSON file to database

        Args:
            json_path: Path to settings.json file
        """
        json_file = Path(json_path)
        if not json_file.exists():
            logger.warning(f"JSON file not found: {json_path}")
            return

        with open(json_file, 'r') as f:
            settings = json.load(f)

        # Flatten and store settings
        self._migrate_dict(settings, prefix='', category='root')
        logger.info(f"Settings migrated from {json_path}")

    def _migrate_dict(self, data: Dict, prefix: str = '', category: str = None):
        """Recursively migrate nested dictionary"""
        for key, value in data.items():
            full_key = f"{prefix}.{key}" if prefix else key

            if isinstance(value, dict):
                # Store the entire dict as a value
                self.set(full_key, value, category=category or key)
            else:
                # Store primitive value
                self.set(full_key, value, category=category or prefix.split('.')[0])

    def export_to_json(self, json_path: str):
        """
        Export settings to JSON file

        Args:
            json_path: Path to save settings.json
        """
        settings = self.get_all()

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

        logger.info(f"Settings exported to {json_path}")

    def _serialize_value(self, value: Any) -> Tuple[str, str]:
        """
        Serialize value to string and determine type

        Returns:
            Tuple of (value_string, value_type)
        """
        if isinstance(value, bool):
            return (json.dumps(value), 'boolean')
        elif isinstance(value, int):
            return (json.dumps(value), 'number')
        elif isinstance(value, float):
            return (json.dumps(value), 'number')
        elif isinstance(value, str):
            return (value, 'string')
        elif isinstance(value, (dict, list)):
            return (json.dumps(value), 'object' if isinstance(value, dict) else 'array')
        else:
            return (json.dumps(value), 'object')

    def _deserialize_value(self, value_str: str, value_type: str) -> Any:
        """Deserialize value from string"""
        if value_type == 'string':
            return value_str
        else:
            return json.loads(value_str)

    def _set_nested(self, data: Dict, key: str, value: Any):
        """Set value in nested dictionary using dot notation"""
        parts = key.split('.')
        current = data

        for part in parts[:-1]:
            if part not in current:
                current[part] = {}
            current = current[part]

        current[parts[-1]] = value
