import copy
import json
import time
from collections import defaultdict

# Processing log — records each operation for debugging and audit trail
_processing_log = []


def _log_operation(stage, reading_id, snapshot):
    """Append an operation record to the processing log."""
    _processing_log.append(
        {
            "stage": stage,
            "reading_id": reading_id,
            "timestamp": time.monotonic(),
            "snapshot": snapshot,
        }
    )


def _snapshot(reading):
    """Create a human-readable snapshot of a reading's current state."""
    return repr(reading.__dict__)


class Reading:
    """A single sensor reading with metadata."""

    def __init__(self, id, sensor_type, timestamp, value, unit, metadata):
        self.id = id
        self.sensor_type = sensor_type
        self.timestamp = timestamp
        self.value = value
        self.unit = unit
        self.metadata = metadata
        self.calibrated = False
        self.validated = False


def parse_readings(raw_data):
    """Parse raw dicts into Reading objects."""
    readings = []
    for item in raw_data:
        reading = Reading(
            id=item["id"],
            sensor_type=item["sensor_type"],
            timestamp=item["timestamp"],
            value=item["value"],
            unit=item.get("unit", "celsius"),
            metadata=item.get("metadata", {}),
        )
        _log_operation("parse", reading.id, _snapshot(reading))
        readings.append(reading)
    return readings


def validate_readings(readings):
    """Validate readings — filter out-of-range or incomplete entries."""
    valid = []
    for reading in readings:
        if reading.value < -1000 or reading.value > 1000:
            _log_operation("validate_reject", reading.id, _snapshot(reading))
            continue
        if not reading.metadata:
            _log_operation("validate_reject", reading.id, _snapshot(reading))
            continue
        reading.validated = True
        _log_operation("validate_pass", reading.id, _snapshot(reading))
        valid.append(reading)
    return valid


def calibrate(readings):
    """Apply per-sensor-type calibration offsets."""
    offsets = {
        f"sensor-{i}": round(0.1 * (i % 5) - 0.2, 2) for i in range(100)
    }

    calibrated = []
    for reading in readings:
        cal = copy.copy(reading)
        offset = offsets.get(reading.sensor_type, 0.0)
        cal.value = round(reading.value + offset, 4)
        cal.calibrated = True
        _log_operation("calibrate", cal.id, _snapshot(cal))
        calibrated.append(cal)
    return calibrated


def compute_statistics(readings):
    """Compute per-sensor-type aggregate statistics."""
    by_type = defaultdict(list)
    for reading in readings:
        by_type[reading.sensor_type].append(reading)

    stats = {}
    for sensor_type, type_readings in by_type.items():
        values = [r.value for r in type_readings]
        stats[sensor_type] = {
            "count": len(values),
            "mean": round(sum(values) / len(values), 4),
            "min": min(values),
            "max": max(values),
            "range": round(max(values) - min(values), 4),
        }
    return stats


def format_report(stats, readings):
    """Format final report with validated per-type summaries."""
    report = {}
    for sensor_type, type_stats in stats.items():
        validated = json.loads(json.dumps(type_stats))
        report[sensor_type] = validated
    return report


def process_readings(raw_data):
    """Main entry point — full sensor data processing pipeline."""
    _processing_log.clear()

    readings = parse_readings(raw_data)
    valid = validate_readings(readings)
    calibrated = calibrate(valid)
    stats = compute_statistics(calibrated)
    report = format_report(stats, calibrated)

    return report
