"""
Common validation functions for sensor data.

Provides data quality checks and filtering.
"""

import numpy as np
import logging
from typing import Tuple, Optional

logger = logging.getLogger(__name__)


def validate_temperature(
    temperature: np.ndarray,
    temp_min: float = -30.0,
    temp_max: float = 80.0
) -> Tuple[np.ndarray, int]:
    """
    Validate temperature readings and mark invalid values.

    Args:
        temperature: Temperature array
        temp_min: Minimum valid temperature
        temp_max: Maximum valid temperature

    Returns:
        Tuple of (validated_temperature, number_of_corrections)
    """
    invalid_mask = (temperature < temp_min) | (temperature > temp_max)
    n_corrections = np.sum(invalid_mask)

    if n_corrections > 0:
        logger.warning(f"{n_corrections} temperature values out of valid range [{temp_min}, {temp_max}]")

    return invalid_mask, n_corrections


def despike_data(
    data: np.ndarray,
    n_points: int = 3,
    threshold: float = 3.0
) -> Tuple[np.ndarray, int]:
    """
    Remove spikes from sensor data using median filtering.

    Args:
        data: Input data array (rows=timestamps, cols=sensors)
        n_points: Number of points to use for median calculation
        threshold: Standard deviation threshold for spike detection

    Returns:
        Tuple of (despiked_data, number_of_spikes_removed)
    """
    if len(data) < n_points:
        return data, 0

    despiked = data.copy()
    n_spikes = 0

    # Calculate rolling median and std
    for i in range(n_points, len(data)):
        window = data[i-n_points:i]
        median = np.median(window, axis=0)
        std = np.std(window, axis=0)

        # Detect spikes
        spike_mask = np.abs(data[i] - median) > threshold * std

        if np.any(spike_mask):
            despiked[i, spike_mask] = median[spike_mask]
            n_spikes += np.sum(spike_mask)

    if n_spikes > 0:
        logger.info(f"Removed {n_spikes} spikes from data")

    return despiked, n_spikes


def check_acceleration_vector(
    acceleration: np.ndarray,
    tolerance: float = 0.05,
    valid_range: Tuple[float, float] = (0.8, 1.3)
) -> Tuple[np.ndarray, int, int]:
    """
    Check acceleration vector magnitude for MEMS sensors.

    Validates that acceleration magnitude is close to 1g and within calibration range.

    Args:
        acceleration: Acceleration magnitude array (rows=timestamps, cols=sensors)
        tolerance: Tolerance for frame-to-frame changes
        valid_range: Valid range for acceleration magnitude (calibration check)

    Returns:
        Tuple of (error_mask, n_tolerance_errors, n_calibration_errors)
    """
    error_mask = np.zeros_like(acceleration, dtype=bool)
    n_tolerance_errors = 0
    n_calibration_errors = 0

    if len(acceleration) < 2:
        return error_mask, 0, 0

    # Check frame-to-frame changes
    diff = np.abs(np.diff(acceleration, axis=0))
    tolerance_errors = diff > tolerance
    error_mask[1:] |= tolerance_errors
    n_tolerance_errors = np.sum(tolerance_errors)

    # Check calibration range
    calibration_errors = (acceleration < valid_range[0]) | (acceleration > valid_range[1])
    error_mask |= calibration_errors
    n_calibration_errors = np.sum(calibration_errors)

    if n_tolerance_errors > 0:
        logger.warning(f"{n_tolerance_errors} acceleration values exceed tolerance threshold")
    if n_calibration_errors > 0:
        logger.warning(f"{n_calibration_errors} acceleration values out of calibration range")

    return error_mask, n_tolerance_errors, n_calibration_errors


def approximate_values(
    *arrays: np.ndarray,
    decimals: int = 3
) -> Tuple[np.ndarray, ...]:
    """
    Round values to specified decimal places.

    Converts MATLAB approx.m function.

    Args:
        arrays: Variable number of numpy arrays to approximate
        decimals: Number of decimal places

    Returns:
        Tuple of rounded arrays
    """
    return tuple(np.round(arr, decimals) for arr in arrays)


def fill_missing_values(
    data: np.ndarray,
    method: str = 'previous'
) -> np.ndarray:
    """
    Fill missing or invalid values in data array.

    Args:
        data: Input data with missing values (marked as NaN)
        method: Method for filling ('previous', 'linear', 'zero')

    Returns:
        Data array with filled values
    """
    filled = data.copy()

    if method == 'previous':
        # Forward fill using previous valid value
        for col in range(filled.shape[1]):
            mask = np.isnan(filled[:, col])
            if np.any(mask):
                # Find first valid value
                valid_idx = np.where(~mask)[0]
                if len(valid_idx) > 0:
                    first_valid = valid_idx[0]
                    # Fill values before first valid with first valid value
                    filled[:first_valid, col] = filled[first_valid, col]
                    # Forward fill the rest
                    for i in range(first_valid + 1, len(filled)):
                        if mask[i]:
                            filled[i, col] = filled[i-1, col]

    elif method == 'linear':
        # Linear interpolation
        for col in range(filled.shape[1]):
            mask = ~np.isnan(filled[:, col])
            if np.sum(mask) >= 2:
                indices = np.arange(len(filled))
                filled[:, col] = np.interp(
                    indices,
                    indices[mask],
                    filled[mask, col]
                )

    elif method == 'zero':
        # Fill with zeros
        filled[np.isnan(filled)] = 0.0

    return filled


def validate_battery_level(
    battery_level: float,
    warning_threshold: float = 20.0,
    critical_threshold: float = 10.0
) -> str:
    """
    Validate battery level and return status.

    Args:
        battery_level: Battery level in percentage
        warning_threshold: Warning threshold percentage
        critical_threshold: Critical threshold percentage

    Returns:
        Status string: 'ok', 'warning', or 'critical'
    """
    if battery_level <= critical_threshold:
        logger.error(f"Battery level critical: {battery_level}%")
        return 'critical'
    elif battery_level <= warning_threshold:
        logger.warning(f"Battery level low: {battery_level}%")
        return 'warning'
    else:
        return 'ok'
