# (c) cavaliba.com - home - configuration.py

import sys

from django.conf import settings

import app_home.cache as cache

from .configuration_default import CONFIGURATION_DEFAULT
from .configuration_form import (
    AppDataConfigurationForm,
    AppHomeConfigurationForm,
    AppIpamConfigurationForm,
    AppSireneConfigurationForm,
    AppStatusConfigurationForm,
    AppUserConfigurationForm,
)
from .models import CavalibaConfiguration

# --------------------------------------------
# cache / get / set
# --------------------------------------------


def load_configuration_cache():

    cache.cache_configuration = {}

    # first, load default
    for appname, appconfig in CONFIGURATION_DEFAULT.items():
        cache.cache_configuration[appname] = {}
        for k, v in appconfig.items():
            cache.cache_configuration[appname][k] = v

    # then, load from db
    dbconf = CavalibaConfiguration.objects.all()
    for item in dbconf:
        if item.appname not in cache.cache_configuration:
            cache.cache_configuration[item.appname] = {}
        cache.cache_configuration[item.appname][item.keyname] = item.value

    # NEXT: store in faster redis cache


def get_configuration(appname="home", keyname=None):

    resp_env = None

    if appname == "env":
        resp_env = getattr(settings, keyname, None)

    if resp_env:
        return resp_env

    if appname in cache.cache_configuration:
        if keyname in cache.cache_configuration[appname]:
            return cache.cache_configuration[appname][keyname]

    load_configuration_cache()

    if appname in cache.cache_configuration:
        if keyname in cache.cache_configuration[appname]:
            return cache.cache_configuration[appname][keyname]

    return


def set_configuration(
    init_only=False, appname=None, keyname=None, description=None, value=None, order=None, page=None
):

    # init : if True, only create non-existing entries
    if not appname:
        return
    if not keyname:
        return

    dbentry = CavalibaConfiguration.objects.filter(appname=appname, keyname=keyname).first()

    # init only ?
    if init_only:
        if dbentry:
            return

    # create if missing
    if not dbentry:
        dbentry = CavalibaConfiguration()

    # update
    dbentry.appname = appname
    dbentry.keyname = keyname
    dbentry.description = description
    dbentry.page = page
    dbentry.order = order
    dbentry.value = value
    dbentry.save()

    return dbentry


# --------------------------------------------
# CLI
# --------------------------------------------
def conf_list():

    return CavalibaConfiguration.objects.all().order_by("appname")


# --------------------------------------------
# API (app_data.api.conf) - no business logic in the view, it lives here
# --------------------------------------------


def conf_list_export():
    """Return every DB-persisted configuration entry as a flat list of dicts
    (appname, keyname, value, description, page, order), for GET /api/conf/.
    Unlike get_configuration()/cache_configuration, this does not merge in
    CONFIGURATION_DEFAULT or "env" (settings-backed, never DB-persisted)."""

    entries = CavalibaConfiguration.objects.all().order_by("appname", "keyname")

    return [
        {
            "appname": entry.appname,
            "keyname": entry.keyname,
            "value": entry.value,
            "description": entry.description,
            "page": entry.page,
            "order": entry.order,
        }
        for entry in entries
    ]


def conf_upsert_entry(
    appname=None, keyname=None, value=None, description=None, page=None, order=None
):
    """Validate and upsert a single configuration entry, for POST /api/conf/.
    Rejects missing appname/keyname and appname="env" (settings-backed, never
    DB-persisted - see get_configuration()). Returns (ok, error)."""

    if not appname:
        return False, "missing appname"
    if not keyname:
        return False, "missing keyname"
    if appname == "env":
        return False, "appname 'env' is settings-backed and cannot be written via API"

    set_configuration(
        appname=appname,
        keyname=keyname,
        value=value if value is not None else "",
        description=description or "",
        page=page or "",
        order=order if order is not None else 100,
    )

    return True, None


def refresh_configuration_cache():
    """Reload cache_configuration from DB and clear cache2_configuration - the
    same pair save_form() already runs after a UI configuration update."""

    load_configuration_cache()
    cache.cache2_configuration.clear()


# --------------------------------------------
# Form
# --------------------------------------------


def get_post_form(request, appname=None):

    if not appname:
        return

    load_configuration_cache()

    if appname not in cache.cache_configuration:
        return

    if appname == "sirene":
        form = AppSireneConfigurationForm(request.POST)
    elif appname == "home":
        form = AppHomeConfigurationForm(request.POST)
    elif appname == "user":
        form = AppUserConfigurationForm(request.POST)
    elif appname == "data":
        form = AppDataConfigurationForm(request.POST)
    elif appname == "status":
        form = AppStatusConfigurationForm(request.POST)
    elif appname == "ipam":
        form = AppIpamConfigurationForm(request.POST)
    else:
        form = None

    return form


def get_initial_form(appname=None):

    if not appname:
        return

    load_configuration_cache()

    if appname not in cache.cache_configuration:
        return

    initial = {}
    for k, v in cache.cache_configuration[appname].items():
        initial[k] = v

    if appname == "sirene":
        form = AppSireneConfigurationForm(initial=initial)
    elif appname == "home":
        form = AppHomeConfigurationForm(initial=initial)
    elif appname == "user":
        form = AppUserConfigurationForm(initial=initial)
    elif appname == "data":
        form = AppDataConfigurationForm(initial=initial)
    elif appname == "status":
        form = AppStatusConfigurationForm(initial=initial)
    elif appname == "ipam":
        form = AppIpamConfigurationForm(initial=initial)
    else:
        form = None
    return form


def save_form(form, appname=None):
    """Persist only the submitted fields whose cleaned value actually differs
    from the currently stored one, via conf_upsert_entry() - the same write
    path POST /api/conf/ uses. Returns the list of keynames that were
    changed, so the caller can log one entry per actual change instead of one
    aggregate log per save regardless of what (if anything) changed."""

    changed = []

    if not appname:
        return changed

    load_configuration_cache()

    if appname not in cache.cache_configuration:
        return changed

    if appname not in CONFIGURATION_DEFAULT:
        return changed

    appconfig = CONFIGURATION_DEFAULT[appname]

    for k, v in form.cleaned_data.items():
        if k not in appconfig:
            continue

        dbentry = CavalibaConfiguration.objects.filter(appname=appname, keyname=k).first()
        current_value = dbentry.value if dbentry else None

        if str(v) == str(current_value):
            continue

        conf_upsert_entry(
            appname=appname,
            keyname=k,
            value=v,
            description=dbentry.description if dbentry else "",
            page=dbentry.page if dbentry else "",
            order=dbentry.order if dbentry else 100,
        )
        changed.append(k)

    if changed:
        refresh_configuration_cache()

    return changed


# -------------------------------------------------------------------
# commands
# -------------------------------------------------------------------


def sync_configuration(verbose=False):
    """update configuration - purge / add new default conf entries"""

    r1 = purge_configuration_default(verbose=verbose)
    if verbose:
        sys.stdout.write(f"configuration: {r1} orphans purged\n\n")

    r2 = add_configuration_default(verbose=verbose)
    if verbose:
        sys.stdout.write(f"configuration: {r2} new entries created\n\n")


# no direct call
def add_configuration_default(verbose=True):
    # add new/missing configuration entries with default values

    count = 0

    for appname, appconfig in CONFIGURATION_DEFAULT.items():
        # env objects from env, not from DB
        if appname == "env":
            continue

        for keyname, value in appconfig.items():
            dbentry = set_configuration(
                init_only=True,
                appname=appname,
                keyname=keyname,
                description="",
                value=value,
                order=100,
                page="",
            )
            if dbentry:
                count += 1
                if verbose:
                    sys.stdout.write(f"ADDED - {appname} - {keyname} - {value}\n")
            else:
                if verbose:
                    sys.stdout.write(f"NO CHANGE - {appname} - {keyname} - {value}\n")

    return count


# no direct call
def purge_configuration_default(verbose=True):
    """remove orphans conf entries"""

    count = 0

    dbentries = CavalibaConfiguration.objects.all()

    for dbentry in dbentries:
        appname = dbentry.appname
        keyname = dbentry.keyname
        value = dbentry.value

        # patch: env entries mustn't be in DB
        if dbentry.appname == "env":
            dbentry.delete()
            count += 1
            if verbose:
                sys.stdout.write(f"DELETE - {appname} - {keyname} - {value}\n")

        # app not existing
        elif dbentry.appname not in CONFIGURATION_DEFAULT:
            dbentry.delete()
            count += 1
            if verbose:
                sys.stdout.write(f"DELETE - {appname} - {keyname} - {value}\n")

        # entry for app not existing
        else:
            if dbentry.keyname not in CONFIGURATION_DEFAULT[dbentry.appname]:
                dbentry.delete()
                count += 1
                if verbose:
                    sys.stdout.write(f"DELETE - {appname} - {keyname} - {value}\n")
            else:
                if verbose:
                    sys.stdout.write(f"NO CHANGE - {appname} - {keyname} - {value}\n")
    return count
