"""Constants."""

from __future__ import annotations

from collections.abc import Callable
from datetime import timedelta
from enum import Enum
from typing import Final

CALLBACK_TYPE = Callable[[], None]

SOURCE_LOCAL: Final = "local"

START_TIMEOUT = 15
STOP_TIMEOUT = 5

# The maximum time between advertisements for a device to be considered
# stale when the advertisement tracker cannot determine the interval.
#
# We have to set this quite high as we don't know
# when devices fall out of the ESPHome device (and other non-local scanners)'s
# stack like we do with BlueZ so its safer to assume its available
# since if it does go out of range and it is in range
# of another device the timeout is much shorter and it will
# switch over to using that adapter anyways.
#
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS: Final = 60 * 15

# The maximum time between advertisements for a device to be considered
# stale when the advertisement tracker can determine the interval for
# connectable devices.
#
# BlueZ uses 180 seconds by default but we give it a bit more time
# to account for the esp32's bluetooth stack being a bit slower
# than BlueZ's.
CONNECTABLE_FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS: Final = 195

# When a different scanner's advertisement arrives after the owner's last
# advertisement is stale, the owner is handed off immediately only if the
# challenger is comparable-or-stronger (within ADV_RSSI_SWITCH_THRESHOLD of
# the owner). A materially weaker scanner must wait until the owner has been
# silent for stale_seconds * this factor (capped at
# FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS) before it can take over. This
# stops a far weaker proxy from stealing ownership on a single missed
# interval and surfacing a stale capture for scan-response-only sensors
# (e.g. Govee H5074) seen by many active proxies, while still letting a
# device that genuinely moved into weak-only coverage hand off.
DURABLY_GONE_STALE_FACTOR: Final = 2.5

# An owner whose last advertisement was at least this strong is treated as
# close/stationary: a brief reception gap is almost certainly RF/scan-response
# jitter rather than the device leaving, so a merely-comparable challenger
# (not ADV_RSSI_SWITCH_THRESHOLD stronger) must wait for durable absence
# before stealing ownership, instead of flapping the device on a single
# missed interval. A weaker owner keeps the normal comparable-on-stale
# handoff. Above the -100 owners used in the stale tests so they are
# unaffected, and below typical close-proxy readings.
STRONG_OWNER_STALE_RSSI: Final = -70

# Smoothing factor (EWMA alpha) for the per-(address, source) advertisement
# RSSI used to choose a device's owning scanner. Ownership switches on the
# smoothed value rather than a single sample, so a stationary device whose
# RSSI to a fixed proxy fades by multipath does not flap between proxies on
# momentary spikes; a genuine move still crosses once the average shifts.
# Smaller = steadier but slower to follow a real move. At 0.3 a single spike
# moves the smoothed value ~30% of the gap, so even a 30 dB spike shifts it
# under the 16 dB switch threshold.
RSSI_SMOOTHING_FACTOR: Final = 0.3


# We must recover before we hit the 180s mark
# where the device is removed from the stack
# or the devices will go unavailable. Since
# we only check every 30s, we need this number
# to be
# 180s Time when device is removed from stack
# - 30s check interval
# - 30s scanner restart time * 2
#
SCANNER_WATCHDOG_TIMEOUT: Final = 90
# How often to check if the scanner has reached
# the SCANNER_WATCHDOG_TIMEOUT without seeing anything
SCANNER_WATCHDOG_INTERVAL: Final = timedelta(seconds=30)


UNAVAILABLE_TRACK_SECONDS: Final = 60 * 5


# AUTO scanning mode: each scanner gets its first sweep
# AUTO_INITIAL_SWEEP_DELAY after joining, then every
# AUTO_REDISCOVERY_INTERVAL, serialized across scanners.
AUTO_INITIAL_SWEEP_DELAY: Final = 60 * 4
AUTO_REDISCOVERY_INTERVAL: Final = 60 * 60 * 12
AUTO_REDISCOVERY_SWEEP_DURATION: Final = 15.0

# Per-callback scan_duration is clamped into this range. The floor
# matches the validation in async_register_active_scan; the ceiling is
# the longest single ACTIVE flip we'll ever do for one device tick.
AUTO_WINDOW_MIN_DURATION: Final = 5.0
AUTO_WINDOW_MAX_DURATION: Final = 35.0

# Per-device entries due within AUTO_COALESCE_LOOKAHEAD of now are
# pulled into the current window so staggered registrations sync up
# instead of triggering back-to-back active flips. Must exceed
# AUTO_WINDOW_MAX_DURATION so a window can never outlive its
# lookahead; the slop absorbs loop.time drift between bucket
# collection and window open under a blocked event loop.
AUTO_COALESCE_LOOKAHEAD_SLOP: Final = 5.0
AUTO_COALESCE_LOOKAHEAD: Final = AUTO_WINDOW_MAX_DURATION + AUTO_COALESCE_LOOKAHEAD_SLOP

# Minimum values accepted by async_register_active_scan. Anything
# shorter would just churn the radio without giving the device time to
# respond on its scan response.
MIN_ACTIVE_SCAN_INTERVAL: Final = 60.0
MIN_ACTIVE_SCAN_DURATION: Final = 5.0

# Defaults used by async_register_active_scan when the caller does
# not specify a cadence. One 10s active window every 5 minutes per
# device covers the typical temperature/humidity/battery sensor case
# without burning the proxy's radio or the sensor's battery; an
# integration that genuinely needs faster updates can pass a smaller
# scan_interval explicitly.
DEFAULT_ACTIVE_SCAN_INTERVAL: Final = 300.0
DEFAULT_ACTIVE_SCAN_DURATION: Final = 10.0

# Default duration for an on-demand sweep triggered by
# BluetoothManager.async_request_active_scan (HA config-flow discovery).
# 10s gives every device on the bus a chance to advertise during the
# window without holding the caller too long.
DEFAULT_ON_DEMAND_SWEEP_DURATION: Final = 10.0


FAILED_ADAPTER_MAC = "00:00:00:00:00:00"


ADV_RSSI_SWITCH_THRESHOLD: Final = 16
# The switch threshold for the rssi value
# to switch to a different adapter for advertisements
# Note that this does not affect the connection
# selection that uses RSSI_SWITCH_THRESHOLD from
# bleak_retry_connector

# Extra margin, on top of ADV_RSSI_SWITCH_THRESHOLD, that a challenger must beat
# the current owner by to RECLAIM ownership it was just demoted from, on the
# active RSSI path (both scanners reporting). This is asymmetric hysteresis:
# smoothing removes single-sample noise, but a heavily-multipathed stationary
# device whose smoothed RSSI to two proxies genuinely straddles the 16 dB line
# still oscillates, because each leg of the A->B->A ping-pong only needs 16 dB.
# Charging 16 + deadband only to the source that was just demoted widens the
# band its smoothed signal must swing back through to reclaim, killing that
# residual ping-pong, while a genuine one-way move (never the demoted source)
# still hands off at the plain 16 dB threshold and is not slowed. The stale-path
# "comparable" handoff deliberately does not use this; a silent owner should
# still be easy to replace.
ADV_RSSI_SWITCH_DEADBAND: Final = 6


# Connection parameter constants (units of 1.25ms for intervals)
# Fast connection parameters for initial connection and service discovery
FAST_MIN_CONN_INTERVAL: Final = 0x06  # 6 * 1.25ms = 7.5ms (BLE minimum)
FAST_MAX_CONN_INTERVAL: Final = 0x06  # 6 * 1.25ms = 7.5ms
FAST_CONN_LATENCY: Final = 0  # No latency for fast response
FAST_CONN_TIMEOUT: Final = 1000  # 1000 * 10ms = 10s

# Medium connection parameters for standard operation
# Balanced for stability with WiFi-based BLE proxies
MEDIUM_MIN_CONN_INTERVAL: Final = 0x07  # 7 * 1.25ms = 8.75ms
MEDIUM_MAX_CONN_INTERVAL: Final = 0x09  # 9 * 1.25ms = 11.25ms
MEDIUM_CONN_LATENCY: Final = 0  # No latency
MEDIUM_CONN_TIMEOUT: Final = 800  # 800 * 10ms = 8s

# Bluetooth address types
BDADDR_BREDR: Final = 0x00
BDADDR_LE_PUBLIC: Final = 0x01
BDADDR_LE_RANDOM: Final = 0x02


class ConnectParams(Enum):
    """Connection parameter presets."""

    FAST = "fast"
    MEDIUM = "medium"
