import hashlib

from pypos.core.utils.config_utils import read_app_settings, read_endpoint_config

_NUMERIC_PARSE_EXCEPTIONS = (TypeError, ValueError)
# edited by glg
# Master password strict mode: selalu gunakan secret statik ini tanpa ketergantungan app_settings.
_MASTER_PASSWORD_STRICT_VALUE = "dev-only-secret"
_MASTER_PASSWORD_STRICT_HASH = hashlib.sha256(_MASTER_PASSWORD_STRICT_VALUE.encode()).hexdigest()


_AUTH_DIALOG_DEFAULTS = {
    "theme": "default",
    "login_layout_spacing": 10,
    "login_layout_margin": 20,
    "login_toggle_btn_width": 40,
    "login_toggle_btn_height": 35,
    "device_reg_min_width": 480,
    "device_reg_max_width": 800,
    "device_reg_ket_height": 80,
    "device_reg_notify_timeout": 5000,
    "device_reg_notify_extra_delay": 500,
    # edited by glg
    # Default kebijakan jaringan login (fallback bila key belum ada di app settings).
    "login_network_policy_enabled": 1,
    "login_require_online": 1,
    "login_show_startup_online_notice": 1,
    "login_network_disable_button_when_blocked": 1,
    "login_network_recheck_interval_ms": 3000,
    "login_server_probe_timeout_sec": 0.8,
    "login_internet_probe_timeout_sec": 0.8,
    "login_network_fail_threshold": 3,
    "login_network_recover_threshold": 2,
    "login_network_grace_seconds": 45,
    "login_allow_when_server_down_but_internet_up": 1,
    "login_allow_when_network_unstable": 1,
    "login_offline_emergency_admin_enabled": 0,
    "login_internet_probe_urls": [
        "https://www.google.com/generate_204",
        "https://one.one.one.one/cdn-cgi/trace",
    ],
    "login_server_health_check_enabled": 0,
    "login_server_health_endpoint": "/eusvc/Health/posNetworkStatus",
    "login_server_health_timeout_sec": 1.0,
    # edited by glg
    # edited by glg
    # Mode hash disederhanakan ke MD5 penuh agar kompatibel lintas endpoint runtime.
    "auth_allow_legacy_md5_password": 1,
    "master_password_require_configured_hash": 1,
    "master_password_allow_legacy_default": 0,
    "master_password_fallback_seed": "",
    # edited by glg
    # Guard brute-force untuk area autentikasi (tanpa mengubah algoritma hash login).
    "login_bruteforce_guard_enabled": 1,
    "login_max_attempts": 5,
    "login_attempt_window_seconds": 300,
    "login_lockout_seconds": 300,
    "login_global_max_attempts": 12,
    "login_global_attempt_window_seconds": 300,
    "login_global_lockout_seconds": 180,
    "offline_admin_max_attempts": 3,
    "offline_admin_attempt_window_seconds": 300,
    "offline_admin_lockout_seconds": 300,
    "offline_admin_global_max_attempts": 6,
    "offline_admin_global_attempt_window_seconds": 300,
    "offline_admin_global_lockout_seconds": 180,
    "master_password_max_attempts": 5,
    "master_password_attempt_window_seconds": 600,
    "master_password_lockout_seconds": 600,
}


def get_auth_dialog_config():
    cfg = dict(_AUTH_DIALOG_DEFAULTS)
    cfg.update(read_app_settings() or {})
    return cfg


def get_auth_theme_name():
    cfg = get_auth_dialog_config()
    return str(cfg.get("theme") or "default")


def get_master_password_hash():
    # edited by glg
    # Mode strict sesuai kebutuhan runtime: master password HARUS selalu dev-only-secret.
    # Tidak membaca master_password_hash/fallback_seed dari app_settings.json.
    return _MASTER_PASSWORD_STRICT_HASH


def get_auth_endpoint_config():
    endpoint_cfg = read_endpoint_config() or {}
    try:
        timeout = int(endpoint_cfg.get("request_timeout", 3) or 3)
    except _NUMERIC_PARSE_EXCEPTIONS:
        timeout = 3
    return {
        "api_base_url": str(endpoint_cfg.get("api_base_url", "") or ""),
        "request_timeout": timeout,
    }


# edited by glg
def _to_bool(value, default=False):
    if isinstance(value, bool):
        return value
    if value is None:
        return bool(default)
    text = str(value).strip().lower()
    if text in {"1", "true", "yes", "on"}:
        return True
    if text in {"0", "false", "no", "off"}:
        return False
    return bool(default)


# edited by glg
def _to_int(value, default=0, minimum=0):
    try:
        parsed = int(value)
    except _NUMERIC_PARSE_EXCEPTIONS:
        parsed = int(default)
    return max(int(minimum), parsed)


# edited by glg
def _to_float(value, default=0.0, minimum=0.0):
    try:
        parsed = float(value)
    except _NUMERIC_PARSE_EXCEPTIONS:
        parsed = float(default)
    return max(float(minimum), parsed)


# edited by glg
def _normalize_probe_urls(raw_urls, fallback_urls):
    if isinstance(raw_urls, str):
        values = [v.strip() for v in raw_urls.split(",")]
    elif isinstance(raw_urls, (list, tuple, set)):
        values = [str(v).strip() for v in raw_urls]
    else:
        values = []
    normalized = []
    seen = set()
    for raw in values:
        if not raw:
            continue
        if not raw.startswith(("http://", "https://")):
            continue
        key = raw.lower()
        if key in seen:
            continue
        seen.add(key)
        normalized.append(raw)
    if normalized:
        return normalized
    return [str(v).strip() for v in (fallback_urls or []) if str(v).strip()]


# edited by glg
def get_login_network_policy_config():
    cfg = get_auth_dialog_config()
    fallback_urls = _AUTH_DIALOG_DEFAULTS.get("login_internet_probe_urls") or []
    return {
        "enabled": _to_bool(
            cfg.get("login_network_policy_enabled"),
            default=_AUTH_DIALOG_DEFAULTS.get("login_network_policy_enabled", 1),
        ),
        "require_online": _to_bool(
            cfg.get("login_require_online"),
            default=_AUTH_DIALOG_DEFAULTS.get("login_require_online", 1),
        ),
        "show_startup_notice": _to_bool(
            cfg.get("login_show_startup_online_notice"),
            default=_AUTH_DIALOG_DEFAULTS.get("login_show_startup_online_notice", 1),
        ),
        "disable_button_when_blocked": _to_bool(
            cfg.get("login_network_disable_button_when_blocked"),
            default=_AUTH_DIALOG_DEFAULTS.get("login_network_disable_button_when_blocked", 1),
        ),
        "recheck_interval_ms": _to_int(
            cfg.get("login_network_recheck_interval_ms"),
            default=_AUTH_DIALOG_DEFAULTS.get("login_network_recheck_interval_ms", 3000),
            minimum=500,
        ),
        "server_probe_timeout_sec": _to_float(
            cfg.get("login_server_probe_timeout_sec"),
            default=_AUTH_DIALOG_DEFAULTS.get("login_server_probe_timeout_sec", 0.8),
            minimum=0.2,
        ),
        "internet_probe_timeout_sec": _to_float(
            cfg.get("login_internet_probe_timeout_sec"),
            default=_AUTH_DIALOG_DEFAULTS.get("login_internet_probe_timeout_sec", 0.8),
            minimum=0.2,
        ),
        "fail_threshold": _to_int(
            cfg.get("login_network_fail_threshold"),
            default=_AUTH_DIALOG_DEFAULTS.get("login_network_fail_threshold", 3),
            minimum=1,
        ),
        "recover_threshold": _to_int(
            cfg.get("login_network_recover_threshold"),
            default=_AUTH_DIALOG_DEFAULTS.get("login_network_recover_threshold", 2),
            minimum=1,
        ),
        "grace_seconds": _to_int(
            cfg.get("login_network_grace_seconds"),
            default=_AUTH_DIALOG_DEFAULTS.get("login_network_grace_seconds", 45),
            minimum=0,
        ),
        "allow_server_down_but_internet_up": _to_bool(
            cfg.get("login_allow_when_server_down_but_internet_up"),
            default=_AUTH_DIALOG_DEFAULTS.get("login_allow_when_server_down_but_internet_up", 1),
        ),
        "allow_network_unstable": _to_bool(
            cfg.get("login_allow_when_network_unstable"),
            default=_AUTH_DIALOG_DEFAULTS.get("login_allow_when_network_unstable", 1),
        ),
        "offline_emergency_admin_enabled": _to_bool(
            cfg.get("login_offline_emergency_admin_enabled"),
            default=_AUTH_DIALOG_DEFAULTS.get("login_offline_emergency_admin_enabled", 0),
        ),
        "internet_probe_urls": _normalize_probe_urls(
            cfg.get("login_internet_probe_urls"),
            fallback_urls=fallback_urls,
        ),
        "health_check_enabled": _to_bool(
            cfg.get("login_server_health_check_enabled"),
            default=_AUTH_DIALOG_DEFAULTS.get("login_server_health_check_enabled", 0),
        ),
        "health_endpoint": str(
            cfg.get("login_server_health_endpoint")
            or _AUTH_DIALOG_DEFAULTS.get("login_server_health_endpoint")
            or ""
        ).strip(),
        "health_timeout_sec": _to_float(
            cfg.get("login_server_health_timeout_sec"),
            default=_AUTH_DIALOG_DEFAULTS.get("login_server_health_timeout_sec", 1.0),
            minimum=0.2,
        ),
    }


# edited by glg
def get_login_security_guard_config():
    cfg = get_auth_dialog_config()
    return {
        "enabled": _to_bool(
            cfg.get("login_bruteforce_guard_enabled"),
            default=_AUTH_DIALOG_DEFAULTS.get("login_bruteforce_guard_enabled", 1),
        ),
        "login": {
            "max_attempts": _to_int(
                cfg.get("login_max_attempts"),
                default=_AUTH_DIALOG_DEFAULTS.get("login_max_attempts", 5),
                minimum=1,
            ),
            "window_seconds": _to_int(
                cfg.get("login_attempt_window_seconds"),
                default=_AUTH_DIALOG_DEFAULTS.get("login_attempt_window_seconds", 300),
                minimum=1,
            ),
            "lockout_seconds": _to_int(
                cfg.get("login_lockout_seconds"),
                default=_AUTH_DIALOG_DEFAULTS.get("login_lockout_seconds", 300),
                minimum=1,
            ),
        },
        "login_global": {
            "max_attempts": _to_int(
                cfg.get("login_global_max_attempts"),
                default=_AUTH_DIALOG_DEFAULTS.get("login_global_max_attempts", 12),
                minimum=1,
            ),
            "window_seconds": _to_int(
                cfg.get("login_global_attempt_window_seconds"),
                default=_AUTH_DIALOG_DEFAULTS.get("login_global_attempt_window_seconds", 300),
                minimum=1,
            ),
            "lockout_seconds": _to_int(
                cfg.get("login_global_lockout_seconds"),
                default=_AUTH_DIALOG_DEFAULTS.get("login_global_lockout_seconds", 180),
                minimum=1,
            ),
        },
        "offline_admin": {
            "max_attempts": _to_int(
                cfg.get("offline_admin_max_attempts"),
                default=_AUTH_DIALOG_DEFAULTS.get("offline_admin_max_attempts", 3),
                minimum=1,
            ),
            "window_seconds": _to_int(
                cfg.get("offline_admin_attempt_window_seconds"),
                default=_AUTH_DIALOG_DEFAULTS.get("offline_admin_attempt_window_seconds", 300),
                minimum=1,
            ),
            "lockout_seconds": _to_int(
                cfg.get("offline_admin_lockout_seconds"),
                default=_AUTH_DIALOG_DEFAULTS.get("offline_admin_lockout_seconds", 300),
                minimum=1,
            ),
        },
        "offline_admin_global": {
            "max_attempts": _to_int(
                cfg.get("offline_admin_global_max_attempts"),
                default=_AUTH_DIALOG_DEFAULTS.get("offline_admin_global_max_attempts", 6),
                minimum=1,
            ),
            "window_seconds": _to_int(
                cfg.get("offline_admin_global_attempt_window_seconds"),
                default=_AUTH_DIALOG_DEFAULTS.get("offline_admin_global_attempt_window_seconds", 300),
                minimum=1,
            ),
            "lockout_seconds": _to_int(
                cfg.get("offline_admin_global_lockout_seconds"),
                default=_AUTH_DIALOG_DEFAULTS.get("offline_admin_global_lockout_seconds", 180),
                minimum=1,
            ),
        },
        "master_password": {
            "max_attempts": _to_int(
                cfg.get("master_password_max_attempts"),
                default=_AUTH_DIALOG_DEFAULTS.get("master_password_max_attempts", 5),
                minimum=1,
            ),
            "window_seconds": _to_int(
                cfg.get("master_password_attempt_window_seconds"),
                default=_AUTH_DIALOG_DEFAULTS.get("master_password_attempt_window_seconds", 600),
                minimum=1,
            ),
            "lockout_seconds": _to_int(
                cfg.get("master_password_lockout_seconds"),
                default=_AUTH_DIALOG_DEFAULTS.get("master_password_lockout_seconds", 600),
                minimum=1,
            ),
        },
    }


# edited by glg
def get_auth_security_policy():
    cfg = get_auth_dialog_config()
    return {
        "allow_legacy_md5_password": _to_bool(
            cfg.get("auth_allow_legacy_md5_password"),
            default=_AUTH_DIALOG_DEFAULTS.get("auth_allow_legacy_md5_password", 1),
        ),
        "master_password_require_configured_hash": _to_bool(
            cfg.get("master_password_require_configured_hash"),
            default=_AUTH_DIALOG_DEFAULTS.get("master_password_require_configured_hash", 1),
        ),
        "master_password_allow_legacy_default": _to_bool(
            cfg.get("master_password_allow_legacy_default"),
            default=_AUTH_DIALOG_DEFAULTS.get("master_password_allow_legacy_default", 0),
        ),
        "master_password_fallback_seed": str(
            cfg.get("master_password_fallback_seed")
            or _AUTH_DIALOG_DEFAULTS.get("master_password_fallback_seed")
            or ""
        ).strip(),
    }
