import json
from typing import Any, Optional

from loguru import logger

from lnbits.core.db import db
from lnbits.db import dict_to_model
from lnbits.settings import (
    AdminSettings,
    EditableSettings,
    SettingsField,
    SuperSettings,
    settings,
)


async def get_super_settings() -> Optional[SuperSettings]:
    data = await get_settings_by_tag("core")
    if data:
        super_user = await get_settings_field("super_user")
        super_user_id = super_user.value if super_user else None
        settings_dict = {"super_user": super_user_id, **data}
        return dict_to_model(settings_dict, SuperSettings)
    return None


async def get_admin_settings(is_super_user: bool = False) -> Optional[AdminSettings]:
    sets = await get_super_settings()
    if not sets:
        return None
    row_dict = dict(sets)
    row_dict.pop("super_user")
    row_dict.pop("auth_all_methods")

    admin_settings = AdminSettings(
        is_super_user=is_super_user,
        lnbits_allowed_funding_sources=settings.lnbits_allowed_funding_sources,
        **row_dict,
    )
    return admin_settings


async def update_admin_settings(
    data: EditableSettings, tag: Optional[str] = "core"
) -> None:
    editable_settings = await get_settings_by_tag("core") or {}
    editable_settings.update(data.dict(exclude_unset=True))
    for key, value in editable_settings.items():
        try:
            await set_settings_field(key, value, tag)
        except Exception as exc:
            logger.warning(exc)
            logger.warning(f"Failed to update settings for '{tag}.{key}'.")


async def update_super_user(super_user: str) -> SuperSettings:
    await set_settings_field("super_user", super_user)
    settings = await get_super_settings()
    assert settings, "updated super_user settings could not be retrieved"
    return settings


async def delete_admin_settings(tag: Optional[str] = "core") -> None:
    await db.execute("DELETE FROM settings WHERE tag = :tag", {"tag": tag})


async def create_admin_settings(super_user: str, new_settings: dict) -> SuperSettings:
    data = {"super_user": super_user, **new_settings}
    for key, value in data.items():
        await set_settings_field(key, value)

    settings = await get_super_settings()
    assert settings, "created admin settings could not be retrieved"
    return settings


async def get_settings_field(
    id_: str, tag: Optional[str] = "core"
) -> Optional[SettingsField]:

    row: dict = await db.fetchone(
        """
            SELECT * FROM system_settings
            WHERE  id = :id AND tag = :tag
        """,
        {"id": id_, "tag": tag},
    )
    if not row:
        return None
    return SettingsField(id=row["id"], value=json.loads(row["value"]), tag=row["tag"])


async def set_settings_field(
    id_: str, value: Optional[Any], tag: Optional[str] = "core"
):
    value = json.dumps(value) if value is not None else None
    await db.execute(
        """
        INSERT INTO system_settings (id, value, tag)
        VALUES (:id, :value, :tag)
        ON CONFLICT (id, tag) DO UPDATE SET value = :value
        """,
        {"id": id_, "value": value, "tag": tag or "core"},
    )


async def get_settings_by_tag(tag: str) -> Optional[dict[str, Any]]:
    rows: list[dict] = await db.fetchall(
        "SELECT * FROM system_settings WHERE tag = :tag", {"tag": tag}
    )
    if len(rows) == 0:
        return None
    data: dict[str, Any] = {}
    for row in rows:
        try:
            data[row["id"]] = json.loads(row["value"]) if row["value"] else None
        except Exception as _:
            logger.warning(
                f"""Failed to load settings value for '{tag}.{row["id"]}'."""
            )
    data.pop("super_user")
    return data
