import os
import statistics
from datetime import datetime

# Более лучшая версия кода есть в FRScanner


class DataBuffer:
    """
    Класс с реализацией циклического буффера.

    Атрибуты:
            current_column: Указатель на текущий столбец буфера, который обновляем.
            thinning_counter: Прореживающий множитель на текующей итерации.
            current_counter: Указатель на количество чтений между последним обновлением столбца и предыдущим атрибутом.
            num_of_thinning_iter: Прореживающий множитель. Раз в это количество раз будет обновляться столбец буфера.
            line_size: Количество строк буфера = количеству каналов.
            columns_size: Количество столбцов = фиксированное число.
            multiply_factor: Процентный показатель превышения сигналом уровня шума (legacy).
            num_for_alarm: Количество раз, превышающих шум, при которых триггеримся.
            is_init: Флаг инициализации буфера.
            buffer: Массив для буфера.
            buffer_medians: Массив медиан по каналам.
            buffer_mads: Массив MAD по каналам.
            buffer_alarms: Массив для количества тревог по каналам.
    """

    def __init__(self, columns_size, num_of_thinning_iter, num_of_channels, multiply_factor, num_for_alarm, freq_tag=None):
        self.current_column = 0
        self.thinning_counter = 1
        self.current_counter = 1
        self.num_of_thinning_iter = num_of_thinning_iter
        self.line_size = num_of_channels
        self.columns_size = columns_size
        self.multiply_factor = multiply_factor
        self.num_for_alarm = num_for_alarm
        self.is_init = False

        self.buffer = [[0 for _ in range(self.columns_size)] for _ in range(self.line_size)]
        self.buffer_timestamps = [[None for _ in range(self.columns_size)] for _ in range(self.line_size)]
        self.buffer_medians = [0.0] * self.line_size
        self.buffer_mads = [0.0] * self.line_size
        self.buffer_alarms = [0] * self.line_size
        self.last_alarm_channels = []
        self.episode_history = [[0.0 for _ in range(self.num_of_thinning_iter)] for _ in range(self.line_size)]
        self.episode_history_timestamps = [[None for _ in range(self.num_of_thinning_iter)] for _ in range(self.line_size)]

        self.freq_tag = '' if freq_tag is None else str(freq_tag)
        suffix = f'_{self.freq_tag}' if self.freq_tag else ''

        self.mad_k_on = float(os.getenv('mad_k_on' + suffix, os.getenv('mad_k_on', 5.0)))
        self.mad_eps = float(os.getenv('mad_eps' + suffix, os.getenv('mad_eps', 0.05)))
        self.dbfs_linear_offset_db = float(
            os.getenv('dbfs_linear_offset_db' + suffix, os.getenv('dbfs_linear_offset_db', 0.0))
        )
        self.dbfs_linear_abs_median_scale = float(
            os.getenv('dbfs_linear_abs_median_scale' + suffix, os.getenv('dbfs_linear_abs_median_scale', 0.0))
        )

    def get_buffer(self):
        return self.buffer

    def get_timestamps(self):
        return self.buffer_timestamps

    def get_medians(self):
        return self.buffer_medians

    def get_mads(self):
        return self.buffer_mads

    def get_alarms(self):
        return self.buffer_alarms

    def get_last_alarm_channels(self):
        return list(self.last_alarm_channels)

    def check_init(self):
        return self.is_init

    def print(self):
        print('buffer is: ')
        for i in range(self.line_size):
            print(self.buffer[i], end=' ')
            print()

    @staticmethod
    def _calc_mad(values, median):
        deviations = [abs(v - median) for v in values]
        return statistics.median(deviations)

    @staticmethod
    def _format_ts(timestamp):
        if timestamp is None:
            return 'None'
        try:
            return datetime.fromtimestamp(float(timestamp)).isoformat(sep=' ', timespec='milliseconds')
        except Exception:
            return str(timestamp)

    @staticmethod
    def _mean_timestamp(timestamps):
        filtered = [float(ts) for ts in timestamps if ts is not None]
        if not filtered:
            return None
        return sum(filtered) / len(filtered)

    def medians(self):
        """
        Вычислить медиану и MAD по строкам буфера.
        :return: None
        """
        if self.check_init():
            for i in range(self.line_size):
                med = float(statistics.median(self.buffer[i]))
                self.buffer_medians[i] = med
                self.buffer_mads[i] = float(self._calc_mad(self.buffer[i], med))

    def get_linear_term(self, median_value):
        median_value = float(median_value)
        return self.dbfs_linear_offset_db + self.dbfs_linear_abs_median_scale * abs(median_value)

    def get_threshold(self, channel_idx):
        """
        Получить динамический порог в dB для канала:
        threshold = median + linear_term(median) + mad_k_on * MAD.
        До завершения инициализации возвращает None.
        """
        if not self.check_init():
            return None

        baseline = float(self.buffer_medians[channel_idx])
        mad_eff = max(float(self.buffer_mads[channel_idx]), self.mad_eps)
        linear_term = self.get_linear_term(baseline)
        return baseline + linear_term + self.mad_k_on * mad_eff

    def get_thresholds(self):
        if not self.check_init():
            return [None] * self.line_size
        return [self.get_threshold(i) for i in range(self.line_size)]

    def log_threshold_update(self, updated_column):
        if not self.check_init():
            return

        now_str = datetime.now().isoformat(sep=' ', timespec='milliseconds')
        freq_tag = self.freq_tag or 'unknown'
        print(f'[threshold-update][{freq_tag}] now={now_str} updated_column={updated_column}')
        for i in range(self.line_size):
            baseline = float(self.buffer_medians[i])
            mad = float(self.buffer_mads[i])
            mad_eff = max(mad, self.mad_eps)
            linear_term = self.get_linear_term(baseline)
            threshold = self.get_threshold(i)
            packet_times = [self._format_ts(ts) for ts in self.buffer_timestamps[i]]
            print(
                f'  ch={i} median={baseline:.6f} '
                f'linear_term={linear_term:.6f} '
                f'mad={mad:.6f} mad_eff={mad_eff:.6f} '
                f'mad_term={self.mad_k_on * mad_eff:.6f} '
                f'threshold={threshold:.6f} '
                f'packet_times={packet_times}'
            )

    def alarms_fill_zeros(self):
        self.buffer_alarms = [0] * self.line_size
        self.last_alarm_channels = []

    def update(self, data, packet_timestamps=None):
        """
        Обновление буфера.
        Если номер текущего чтения совпадает с количеством прореживающего множителя на текущем обновлении буфера, то
        1. Обновляем буфер.
        2. Двигаем курсор на след столбец. Если был последний столбец, то двигаем курсор в начало.
        3. Берем медианы по буферу, если он уже проиницализирован.
        4. Сбрасываем счетчик текущих чтений.
        5. Если был последний столбец (и мы уже переключились на первый), то
                Если прореживающий множитель на текующей итерации был единица, то мы иницилизировались.
                До тех пор, пока множитель на итерации меньше фиксированного, увеличиваем в два раза.
        В противном случае - увеличиваем номер чтения.
        :param data: Массив с метриками сигнала по каналам.
        :param packet_timestamps: Времена пакетов SDR для каждой метрики канала.
        :return: None
        """
        if packet_timestamps is None:
            packet_timestamps = [None] * self.line_size
        if len(packet_timestamps) != self.line_size:
            raise ValueError('packet_timestamps length must match number of channels')

        sample_idx = self.current_counter - 1
        for i in range(self.line_size):
            self.episode_history[i][sample_idx] = float(data[i])
            self.episode_history_timestamps[i][sample_idx] = packet_timestamps[i]

        if self.current_counter == self.thinning_counter:
            updated_column = self.current_column
            for i in range(self.line_size):
                samples = self.episode_history[i][:self.thinning_counter]
                timestamps = self.episode_history_timestamps[i][:self.thinning_counter]
                self.buffer[i][self.current_column] = float(sum(samples) / len(samples))
                self.buffer_timestamps[i][self.current_column] = self._mean_timestamp(timestamps)
            self.current_column = (self.current_column + 1) % self.columns_size
            self.medians()
            if self.check_init():
                self.log_threshold_update(updated_column)
            for i in range(self.line_size):
                for j in range(self.thinning_counter):
                    self.episode_history[i][j] = 0.0
                    self.episode_history_timestamps[i][j] = None
            self.current_counter = 1
            if self.current_column == 0:
                if self.thinning_counter == 1:
                    self.is_init = True
                    self.medians()
                    print('Начальная калибровка завершена.')
                    self.log_threshold_update(updated_column)
                if self.thinning_counter < self.num_of_thinning_iter:
                    self.thinning_counter *= 2
        else:
            self.current_counter += 1


    def check_alarm(self, data):
        """
        Проверка триггера системы по dBFS во времени.
        Один порог на канал, набор тревоги и сброс счетчиков как в main.
        """
        if self.check_init():
            self.last_alarm_channels = []
            for i in range(len(data)):
                current = data[i]
                threshold = self.get_threshold(i)
                exceeding = current >= threshold

                if exceeding:
                    self.buffer_alarms[i] += 1
                else:
                    self.buffer_alarms[i] = 0

                if self.buffer_alarms[i] >= self.num_for_alarm:
                    self.last_alarm_channels = [i]
                    self.buffer_alarms = [0] * self.line_size
                    return True

        return False

    def check_single_alarm(self, median, cur_channel):
        """
        Проверка, является ли текущая метрика по каналу превышающей порог.
        :param median: текущая метрика в dBFS.
        :param cur_channel: индекс канала внутри частоты.
        :return: Да/нет.
        """
        if self.check_init():
            threshold = self.get_threshold(cur_channel)
            return median >= threshold

        return False
