import json

from pypos.core.utils.config_utils import APP_SETTINGS_FILE, read_config

# edited by glg

def _read_sync_config():
    return read_config()

def _read_raw_app_settings():
    try:
        with open(APP_SETTINGS_FILE, "r", encoding="utf-8") as handle:
            payload = json.load(handle)
            if isinstance(payload, dict):
                return payload
    except (OSError, ValueError, TypeError):
        pass
    return {}


# edited by glg
def _safe_int(value, default):
    try:
        return int(value)
    except (TypeError, ValueError, OverflowError):
        return int(default)


# edited by glg
def _safe_float(value, default):
    try:
        return float(value)
    except (TypeError, ValueError, OverflowError):
        return float(default)


def _normalize_table_list(raw_tables):
    if isinstance(raw_tables, str):
        values = [v.strip() for v in raw_tables.split(",")]
    elif isinstance(raw_tables, (list, tuple, set)):
        values = [str(v).strip() for v in raw_tables]
    else:
        values = []
    normalized = []
    seen = set()
    for value in values:
        if not value:
            continue
        if value in seen:
            continue
        seen.add(value)
        normalized.append(value)
    return normalized


def get_sync_tables():
    cfg = _read_sync_config()
    tables = _normalize_table_list(cfg.get("sync_tables"))
    if tables:
        return tables
    return [
        "per_cabang_device",
        "per_cabang",
        "per_customers",
        "per_employee",
        "bank",
        "produk",
        "price",
        "diskon",
        "company_profile",
    ]


def get_master_tables():
    cfg = _read_sync_config()
    tables = _normalize_table_list(cfg.get("master_tables"))
    if tables:
        return tables
    return get_sync_tables()


def get_sync_request_timeout():
    cfg = _read_sync_config()
    return max(1, _safe_int(cfg.get("request_timeout") or 30, 30))


def get_sync_check_timeout():
    timeout = get_sync_request_timeout()
    return max(timeout, 5)


def get_sync_data_timeout():
    timeout = get_sync_request_timeout()
    return max(timeout * 4, 20)


def get_sync_cleanup_wait_timeout_ms():
    cfg = _read_sync_config()
    return max(100, _safe_int(cfg.get("sync_cleanup_wait_timeout_ms") or 1000, 1000))


def get_sync_stop_wait_timeout_ms():
    cfg = _read_sync_config()
    return max(100, _safe_int(cfg.get("sync_stop_wait_timeout_ms") or 2000, 2000))


def get_sync_page_size():
    cfg = _read_sync_config()
    return max(100, _safe_int(cfg.get("sync_page_size") or 500, 500))


def get_sync_circuit_breaker_enabled():
    cfg = _read_sync_config()
    return _safe_int(cfg.get("sync_circuit_breaker_enabled") or 1, 1) == 1


def get_sync_circuit_failure_threshold():
    cfg = _read_sync_config()
    return max(1, _safe_int(cfg.get("sync_circuit_failure_threshold") or 3, 3))


def get_sync_circuit_open_seconds():
    cfg = _read_sync_config()
    return max(5, _safe_int(cfg.get("sync_circuit_open_seconds") or 120, 120))


def get_sync_circuit_retryable_statuses():
    cfg = _read_sync_config()
    raw = cfg.get("http_retry_statuses")
    if isinstance(raw, str):
        raw = [v.strip() for v in raw.split(",")]
    elif not isinstance(raw, (list, tuple, set)):
        raw = [408, 429, 500, 502, 503, 504]
    statuses = []
    seen = set()
    for value in raw:
        try:
            status_code = int(value)
        except (TypeError, ValueError, OverflowError):
            continue
        if status_code < 100 or status_code > 599:
            continue
        if status_code in seen:
            continue
        seen.add(status_code)
        statuses.append(status_code)
    if statuses:
        return statuses
    return [408, 429, 500, 502, 503, 504]


# edited by glg
def _read_toggle(cfg, key: str, default: int = 1) -> bool:
    if not isinstance(cfg, dict):
        cfg = {}
    raw = cfg.get(key, default)
    if isinstance(raw, bool):
        return bool(raw)
    text = str(raw).strip().lower()
    if text in {"1", "true", "yes", "on"}:
        return True
    if text in {"0", "false", "no", "off"}:
        return False
    return _safe_int(raw, int(default or 0)) == 1


# edited by glg
def _read_int(cfg, key: str, default: int, minimum: int = 0) -> int:
    if not isinstance(cfg, dict):
        cfg = {}
    value = _safe_int(cfg.get(key, default), default)
    return max(int(minimum), value)


# edited by glg
def _read_float(cfg, key: str, default: float, minimum: float = 0.0) -> float:
    if not isinstance(cfg, dict):
        cfg = {}
    value = _safe_float(cfg.get(key, default), default)
    return max(float(minimum), value)


# edited by glg
def get_network_orchestrator_policy():
    cfg = _read_sync_config()
    internet_probe_urls = cfg.get("login_internet_probe_urls")
    if not isinstance(internet_probe_urls, list):
        internet_probe_urls = []
    return {
        "enabled": bool(_read_toggle(cfg, "network_orchestrator_enabled", 1)),
        "probe_cache_ttl_sec": _read_float(cfg, "network_orchestrator_probe_cache_ttl_sec", 2.0, minimum=0.0),
        "fail_threshold": _read_int(cfg, "network_orchestrator_fail_threshold", 3, minimum=1),
        "recover_threshold": _read_int(cfg, "network_orchestrator_recover_threshold", 2, minimum=1),
        "grace_seconds": _read_float(cfg, "network_orchestrator_grace_seconds", 45.0, minimum=0.0),
        "allow_unstable_operations": bool(
            _read_toggle(cfg, "network_orchestrator_allow_unstable_operations", 1)
        ),
        "allow_degraded_without_server": bool(
            _read_toggle(cfg, "network_orchestrator_allow_degraded_without_server", 0)
        ),
        "process_allow_internet_only": {
            "sync": bool(_read_toggle(cfg, "network_orchestrator_allow_internet_only_sync", 0)),
            "export": bool(_read_toggle(cfg, "network_orchestrator_allow_internet_only_export", 0)),
            "settlement_direct": bool(
                _read_toggle(cfg, "network_orchestrator_allow_internet_only_settlement_direct", 0)
            ),
        },
        "server_probe_timeout_sec": _read_float(
            cfg,
            "network_orchestrator_server_probe_timeout_sec",
            1.0,
            minimum=0.2,
        ),
        "internet_probe_timeout_sec": _read_float(
            cfg,
            "network_orchestrator_internet_probe_timeout_sec",
            0.8,
            minimum=0.2,
        ),
        "internet_probe_urls": internet_probe_urls,
        "health_check_enabled": bool(_read_toggle(cfg, "login_server_health_check_enabled", 0)),
        "health_endpoint": str(cfg.get("login_server_health_endpoint") or "").strip(),
        "health_timeout_sec": _read_float(cfg, "login_server_health_timeout_sec", 1.0, minimum=0.2),
    }


# edited by glg
def is_export_transaksi_enabled():
    cfg = _read_sync_config()
    return bool(_read_toggle(cfg, "export_transaksi_enabled", 1))


# edited by glg
def is_export_transaksi_data_enabled():
    cfg = _read_sync_config()
    return bool(_read_toggle(cfg, "export_transaksi_data_enabled", 1))


# edited by glg
def is_export_transaksi_data_registry_enabled():
    cfg = _read_sync_config()
    return bool(_read_toggle(cfg, "export_transaksi_data_registry_enabled", 1))


def get_export_tables():
    cfg = _read_sync_config()
    tables = _normalize_table_list(cfg.get("export_tables"))
    if not tables:
        tables = [
            "transaksi",
            "transaksi_data",
            # edited by glg
            # Baris return wajib ikut pipeline export.
            "return_transaksi_penjualan",
            "detail_return_transaksi_penjualan",
            "settlement_history",
            "transaksi_settlement",
        ]

    # edited by glg
    # Kompatibilitas isi export lama: jika transaksi+transaksi_data aktif,
    # pastikan transaksi_data_registry ikut diexport.
    if "transaksi" in tables and "transaksi_data" in tables and "transaksi_data_registry" not in tables:
        tables.append("transaksi_data_registry")

    # edited by glg
    # Return penjualan harus selalu ikut payload export transaksi.
    for return_table in ("return_transaksi_penjualan", "detail_return_transaksi_penjualan"):
        if return_table not in tables:
            tables.append(return_table)

    # edited by glg
    # Guard toggle granular export transaksi:
    # dependency:
    # - transaksi OFF => transaksi_data + transaksi_data_registry OFF
    # - transaksi_data OFF => transaksi_data_registry OFF
    enable_transaksi = bool(_read_toggle(cfg, "export_transaksi_enabled", 1))
    enable_transaksi_data = bool(_read_toggle(cfg, "export_transaksi_data_enabled", 1))
    enable_transaksi_registry = bool(_read_toggle(cfg, "export_transaksi_data_registry_enabled", 1))

    if not enable_transaksi:
        tables = [
            name for name in tables
            if name not in {"transaksi", "transaksi_data", "transaksi_data_registry"}
        ]
    elif not enable_transaksi_data:
        tables = [
            name for name in tables
            if name not in {"transaksi_data", "transaksi_data_registry"}
        ]
    elif not enable_transaksi_registry:
        tables = [name for name in tables if name != "transaksi_data_registry"]

    # edited by glg
    # Mode direct_only: cegah settlement table masuk pipeline export reguler
    # agar tidak terjadi double-send (direct + export).
    # Mode export_only/dual tetap mengirim settlement via pipeline export.
    if is_settlement_direct_only_mode():
        settlement_tables = {"settlement_history", "transaksi_settlement"}
        tables = [name for name in tables if name not in settlement_tables]
    return tables


# edited by glg
def get_settlement_delivery_mode():
    cfg = _read_sync_config()
    mode = str(cfg.get("settlement_delivery_mode") or "direct_only").strip().lower()
    if mode not in {"dual", "direct_only", "export_only"}:
        return "dual"
    return mode


# edited by glg
def is_settlement_direct_enabled():
    cfg = _read_sync_config()
    return _safe_int(cfg.get("settlement_direct_enabled") or 0, 0) == 1


# edited by glg
# direct_only hanya aktif jika direct settlement benar-benar enabled.
# Fallback ini mencegah settlement hilang saat mode direct_only tersetel
# tetapi endpoint direct belum aktif.
def is_settlement_direct_only_mode():
    if get_settlement_delivery_mode() != "direct_only":
        return False
    return bool(is_settlement_direct_enabled())


# edited by glg
def get_settlement_direct_timeout_sec():
    cfg = _read_sync_config()
    timeout = _safe_int(
        cfg.get("settlement_direct_timeout_sec") or cfg.get("request_timeout") or 8,
        8,
    )
    return max(1, timeout)


# edited by glg
def get_settlement_direct_retry_max_attempt():
    cfg = _read_sync_config()
    attempts = _safe_int(cfg.get("settlement_direct_retry_max_attempt") or 2, 2)
    return max(1, attempts)


# edited by glg
def get_settlement_direct_retry_backoff_sec():
    cfg = _read_sync_config()
    value = _safe_float(cfg.get("settlement_direct_retry_backoff_sec") or 1.0, 1.0)
    return max(0.0, value)


# edited by glg
def is_settlement_direct_auth_required():
    cfg = _read_sync_config()
    return _safe_int(cfg.get("settlement_direct_auth_required") or 1, 1) == 1


def is_export_upload_enabled():
    cfg = _read_sync_config()
    return _safe_int(cfg.get("export_upload_enabled") or 0, 0) == 1


def get_export_upload_batch_limit():
    cfg = _read_sync_config()
    return max(1, _safe_int(cfg.get("export_upload_batch_limit") or 50, 50))


def get_export_upload_timeout_sec():
    cfg = _read_sync_config()
    return max(
        5,
        _safe_int(cfg.get("export_upload_timeout_sec") or cfg.get("request_timeout") or 30, 30),
    )


def get_export_upload_retry_max_attempt():
    cfg = _read_sync_config()
    return max(1, _safe_int(cfg.get("export_upload_retry_max_attempt") or 8, 8))


def get_export_upload_retry_backoff_base_sec():
    cfg = _read_sync_config()
    return max(1.0, _safe_float(cfg.get("export_upload_retry_backoff_base_sec") or 2, 2.0))


def get_export_upload_retry_backoff_factor():
    cfg = _read_sync_config()
    return max(1.0, _safe_float(cfg.get("export_upload_retry_backoff_factor") or 2, 2.0))


def get_export_upload_retry_backoff_max_sec():
    cfg = _read_sync_config()
    return max(1.0, _safe_float(cfg.get("export_upload_retry_backoff_max_sec") or 300, 300.0))


def get_export_file_retention_days():
    cfg = _read_sync_config()
    raw_cfg = _read_raw_app_settings()
    # edited by glg
    # Prioritas key baru, fallback ke key lama agar upgrade kompatibel.
    if "export_file_retention_days" in raw_cfg:
        return max(0, _safe_int(raw_cfg.get("export_file_retention_days") or 7, 7))
    if "export_upload_file_retention_days" in raw_cfg:
        legacy_value = max(0, _safe_int(raw_cfg.get("export_upload_file_retention_days") or 0, 0))
        # Legacy default lama=30 hari. Saat upgrade otomatis pakai default baru=7.
        if legacy_value == 30:
            return 7
        return legacy_value
    merged_value = cfg.get("export_file_retention_days")
    if merged_value in (None, ""):
        merged_value = 7
    return max(0, _safe_int(merged_value, 7))


def get_export_upload_file_retention_days():
    # edited by glg
    # Alias backward-compatibility untuk import lama.
    return get_export_file_retention_days()


def get_export_flux_empty_retention_days():
    cfg = _read_sync_config()
    return max(0, _safe_int(cfg.get("export_flux_empty_retention_days") or 7, 7))


def get_export_flux_empty_cleanup_limit():
    cfg = _read_sync_config()
    return max(1, _safe_int(cfg.get("export_flux_empty_cleanup_limit") or 500, 500))


def get_export_upload_retryable_reasons():
    cfg = _read_sync_config()
    raw = cfg.get("export_upload_retryable_reasons")
    if isinstance(raw, str):
        items = [v.strip().lower() for v in raw.split(",")]
    elif isinstance(raw, (list, tuple, set)):
        items = [str(v).strip().lower() for v in raw]
    else:
        items = []
    normalized = []
    seen = set()
    for value in items:
        if not value:
            continue
        if value in seen:
            continue
        seen.add(value)
        normalized.append(value)
    if normalized:
        return normalized
    return [
        "timeout",
        "timed out",
        "tempor",
        "server busy",
        "try again",
        "retry",
        "locked",
        "deadlock",
        "too many requests",
    ]


# edited by glg
def get_export_on_close_skip_upload_if_offline():
    cfg = _read_sync_config()
    return _safe_int(cfg.get("export_on_close_skip_upload_if_offline") or 0, 0) == 1


# edited by glg
def get_export_on_close_online_probe_timeout_sec():
    cfg = _read_sync_config()
    return max(0.2, _safe_float(cfg.get("export_on_close_online_probe_timeout_sec") or 1.0, 1.0))


# edited by glg
def get_export_on_close_upload_batch_limit():
    cfg = _read_sync_config()
    return max(1, _safe_int(cfg.get("export_on_close_upload_batch_limit") or 5, 5))


# edited by glg
def get_export_on_close_upload_timeout_sec():
    cfg = _read_sync_config()
    return max(1, _safe_int(cfg.get("export_on_close_upload_timeout_sec") or 5, 5))


# edited by glg
def is_export_replay_on_startup_enabled():
    cfg = _read_sync_config()
    return _safe_int(cfg.get("export_replay_on_startup_enabled") or 0, 0) == 1


# edited by glg
def get_export_replay_on_startup_delay_ms():
    cfg = _read_sync_config()
    return max(0, _safe_int(cfg.get("export_replay_on_startup_delay_ms") or 1500, 1500))


# edited by glg
def get_export_replay_on_startup_require_online():
    cfg = _read_sync_config()
    return _safe_int(cfg.get("export_replay_on_startup_require_online") or 0, 0) == 1


# edited by glg
def get_export_replay_on_startup_upload_batch_limit():
    cfg = _read_sync_config()
    return max(1, _safe_int(cfg.get("export_replay_on_startup_upload_batch_limit") or 25, 25))


# edited by glg
def is_export_requeue_failed_transient_on_startup_enabled():
    cfg = _read_sync_config()
    return _safe_int(cfg.get("export_requeue_failed_transient_on_startup") or 0, 0) == 1


# edited by glg
def get_export_requeue_failed_transient_limit():
    cfg = _read_sync_config()
    return max(1, _safe_int(cfg.get("export_requeue_failed_transient_limit") or 100, 100))


# edited by glg
def get_export_requeue_failed_transient_max_age_hours():
    cfg = _read_sync_config()
    return max(1, _safe_int(cfg.get("export_requeue_failed_transient_max_age_hours") or 72, 72))


def get_export_upload_compile_check_enabled():
    cfg = _read_sync_config()
    endpoint = str(cfg.get("ep_upload_compile_status") or "").strip()
    enabled = _safe_int(cfg.get("export_upload_compile_check_enabled") or 0, 0) == 1
    if enabled:
        return True
    return bool(endpoint)


def get_export_upload_compile_poll_max_attempt():
    cfg = _read_sync_config()
    return max(1, _safe_int(cfg.get("export_upload_compile_poll_max_attempt") or 3, 3))


def get_export_upload_compile_poll_interval_sec():
    cfg = _read_sync_config()
    return max(0.0, _safe_float(cfg.get("export_upload_compile_poll_interval_sec") or 2, 2.0))


def get_export_upload_compile_pending_stuck_attempt_threshold():
    cfg = _read_sync_config()
    return max(
        1,
        _safe_int(cfg.get("export_upload_compile_pending_stuck_attempt_threshold") or 6, 6),
    )


def get_export_upload_compile_pending_stuck_age_minutes():
    cfg = _read_sync_config()
    return max(1, _safe_int(cfg.get("export_upload_compile_pending_stuck_age_minutes") or 30, 30))


def get_export_upload_compile_required_flags():
    cfg = _read_sync_config()
    raw = cfg.get("export_upload_compile_required_flags")
    if isinstance(raw, str):
        items = [v.strip() for v in raw.split(",")]
    elif isinstance(raw, (list, tuple, set)):
        items = [str(v).strip() for v in raw]
    else:
        items = []
    normalized = []
    seen = set()
    for value in items:
        if not value:
            continue
        if value in seen:
            continue
        seen.add(value)
        normalized.append(value)
    if normalized:
        return normalized
    return ["compiled_transaksi", "compiled_data", "compiled_registry"]
