"""Service for persisting and loading system settings from the database.

Settings are stored in KeyValueSqlModel with key="system_settings".
API keys are stored separately with key="api_keys" and encrypted at rest
using Fernet if SETTINGS_ENCRYPTION_KEY is set, otherwise stored in plain text.

On startup, persisted settings are loaded into os.environ so the rest of the
application can read them transparently.
"""
import os
from typing import Optional

try:
    from cryptography.fernet import Fernet
    HAS_FERNET = True
except ImportError:
    HAS_FERNET = False
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from models.sql.key_value import KeyValueSqlModel

SETTINGS_KEY = "system_settings"
API_KEYS_KEY = "api_keys"

# Env vars that map to settings
SETTING_ENV_MAP = {
    "llm_provider": "LLM",
    "llm_model": None,  # Handled specially per-provider
    "image_provider": "IMAGE_PROVIDER",
}

MODEL_ENV_MAP = {
    "anthropic": "ANTHROPIC_MODEL",
    "openai": "OPENAI_MODEL",
    "google": "GOOGLE_MODEL",
    "ollama": "OLLAMA_MODEL",
    "custom": "CUSTOM_MODEL",
}

API_KEY_ENV_MAP = {
    "anthropic_api_key": "ANTHROPIC_API_KEY",
    "openai_api_key": "OPENAI_API_KEY",
    "google_api_key": "GOOGLE_API_KEY",
}


def _get_fernet():
    """Return Fernet cipher if encryption key is configured and cryptography is available."""
    if not HAS_FERNET:
        return None
    key = os.getenv("SETTINGS_ENCRYPTION_KEY")
    if key:
        try:
            return Fernet(key.encode() if isinstance(key, str) else key)
        except Exception:
            pass
    return None


def _encrypt(value: str) -> str:
    f = _get_fernet()
    if f:
        return f.encrypt(value.encode()).decode()
    return value


def _decrypt(value: str) -> str:
    f = _get_fernet()
    if f:
        try:
            return f.decrypt(value.encode()).decode()
        except Exception:
            return value  # Fallback: might be plain text from before encryption was enabled
    return value


async def _get_kv(session: AsyncSession, key: str) -> Optional[dict]:
    """Get a key-value record."""
    stmt = select(KeyValueSqlModel).where(KeyValueSqlModel.key == key)
    result = await session.execute(stmt)
    row = result.scalar_one_or_none()
    return row.value if row else None


async def _set_kv(session: AsyncSession, key: str, value: dict) -> None:
    """Upsert a key-value record."""
    stmt = select(KeyValueSqlModel).where(KeyValueSqlModel.key == key)
    result = await session.execute(stmt)
    row = result.scalar_one_or_none()
    if row:
        row.value = value
        from sqlalchemy.orm.attributes import flag_modified
        flag_modified(row, "value")
    else:
        row = KeyValueSqlModel(key=key, value=value)
        session.add(row)
    await session.commit()


async def load_settings(session: AsyncSession) -> dict:
    """Load persisted settings from DB and apply to os.environ.

    Returns the settings dict. Falls back to env vars if nothing persisted.
    """
    settings = await _get_kv(session, SETTINGS_KEY) or {}
    api_keys = await _get_kv(session, API_KEYS_KEY) or {}

    # Apply settings to env
    if "llm_provider" in settings:
        os.environ["LLM"] = settings["llm_provider"]

    if "llm_model" in settings:
        provider = settings.get("llm_provider", os.getenv("LLM", "anthropic"))
        env_key = MODEL_ENV_MAP.get(provider)
        if env_key:
            os.environ[env_key] = settings["llm_model"]

    if "image_provider" in settings:
        os.environ["IMAGE_PROVIDER"] = settings["image_provider"]

    # Apply API keys (decrypt first)
    for setting_key, env_key in API_KEY_ENV_MAP.items():
        encrypted_val = api_keys.get(setting_key)
        if encrypted_val:
            os.environ[env_key] = _decrypt(encrypted_val)

    return settings


async def save_settings(session: AsyncSession, data: dict) -> list:
    """Persist settings to DB and update os.environ.

    Handles both regular settings and API keys.
    Returns list of changed field names.
    """
    changed = []

    # Load existing
    current_settings = await _get_kv(session, SETTINGS_KEY) or {}
    current_api_keys = await _get_kv(session, API_KEYS_KEY) or {}

    # Process regular settings
    for field in ("llm_provider", "llm_model", "image_provider"):
        if field in data and data[field] is not None:
            current_settings[field] = data[field]
            changed.append(field)

            # Also update env immediately
            if field == "llm_provider":
                os.environ["LLM"] = data[field]
            elif field == "image_provider":
                os.environ["IMAGE_PROVIDER"] = data[field]
            elif field == "llm_model":
                provider = data.get("llm_provider", current_settings.get("llm_provider", os.getenv("LLM", "anthropic")))
                env_key = MODEL_ENV_MAP.get(provider)
                if env_key:
                    os.environ[env_key] = data[field]

    # Process API keys (encrypt before storing)
    for setting_key, env_key in API_KEY_ENV_MAP.items():
        if setting_key in data and data[setting_key] is not None:
            current_api_keys[setting_key] = _encrypt(data[setting_key])
            os.environ[env_key] = data[setting_key]
            changed.append(setting_key)

    # Persist
    if any(f in changed for f in ("llm_provider", "llm_model", "image_provider")):
        await _set_kv(session, SETTINGS_KEY, current_settings)

    if any(f in changed for f in API_KEY_ENV_MAP):
        await _set_kv(session, API_KEYS_KEY, current_api_keys)

    return changed
