import logging

from sqlalchemy import delete as sa_delete, select

from scribe.models import async_session
from scribe.models.setting import Setting
from scribe.models.user import User

logger = logging.getLogger(__name__)

# What a stored credential looks like on the wire. Every surface that READS a
# secret (smtp_password, forge_webhook_secret, a forge token) returns this
# when one is set; every surface that WRITES one treats this value coming back
# as "unchanged", never as a request to store eight asterisks over the real
# credential. One constant so the read and write halves cannot disagree.
SECRET_MASK = "********"


async def get_admin_setting(key: str, default: str = "") -> str:
    """Read an instance-global setting (one stored on an admin account).

    Used for settings that aren't per-user — e.g. the plugin marketplace URL
    shown to everyone in Settings. Mirrors the admin-scoped lookup used for
    the application base URL.
    """
    async with async_session() as session:
        result = await session.execute(
            select(Setting)
            .join(User, Setting.user_id == User.id)
            .where(User.role == "admin", Setting.key == key)
        )
        setting = result.scalars().first()
        return setting.value if setting and setting.value else default


async def set_admin_setting(key: str, value: str) -> None:
    """Write an instance-global setting onto the first admin account.

    The write-side counterpart to get_admin_setting, for non-per-user settings
    written outside a request context (e.g. a scheduler persisting its last-run
    summary) where get_current_user_id() isn't available.
    """
    async with async_session() as session:
        admin_id = (
            await session.execute(
                select(User.id).where(User.role == "admin").order_by(User.id)
            )
        ).scalars().first()
    if admin_id is not None:
        await set_setting(admin_id, key, value)


async def get_setting(user_id: int, key: str, default: str = "") -> str:
    async with async_session() as session:
        result = await session.execute(
            select(Setting).where(Setting.user_id == user_id, Setting.key == key)
        )
        setting = result.scalar_one_or_none()
        return setting.value if setting else default


async def set_setting(user_id: int, key: str, value: str) -> None:
    async with async_session() as session:
        result = await session.execute(
            select(Setting).where(Setting.user_id == user_id, Setting.key == key)
        )
        setting = result.scalar_one_or_none()
        if setting:
            setting.value = value
        else:
            session.add(Setting(user_id=user_id, key=key, value=value))
        await session.commit()


async def set_settings_batch(user_id: int, settings: dict[str, str]) -> None:
    """Update multiple settings in a single transaction."""
    async with async_session() as session:
        for key, value in settings.items():
            result = await session.execute(
                select(Setting).where(Setting.user_id == user_id, Setting.key == key)
            )
            setting = result.scalar_one_or_none()
            if setting:
                setting.value = value
            else:
                session.add(Setting(user_id=user_id, key=key, value=value))
        await session.commit()
    logger.info("Batch-updated %d settings for user %d", len(settings), user_id)


async def delete_setting(user_id: int, key: str) -> None:
    """Remove a setting row so get_setting() returns its hardcoded default instead."""
    async with async_session() as session:
        await session.execute(
            sa_delete(Setting).where(Setting.user_id == user_id, Setting.key == key)
        )
        await session.commit()


async def get_all_settings(user_id: int) -> dict[str, str]:
    async with async_session() as session:
        result = await session.execute(
            select(Setting).where(Setting.user_id == user_id)
        )
        return {s.key: s.value for s in result.scalars().all()}
