# target_simulator/utils/csv_logger.py
"""
CSV helpers for IO tracing.
... (docstring existing)
"""
import csv
import os
import time
import threading
import atexit
from typing import Iterable, Any, Dict, List, Tuple, Optional
from collections import deque

from target_simulator.config import DEBUG_CONFIG

_CSV_BUFFER_LOCK = threading.Lock()
_CSV_BUFFERS: Dict[str, deque] = {}
_CSV_FLUSH_THREAD: Optional[threading.Thread] = None
_CSV_STOP_EVENT = threading.Event()
_CSV_FLUSH_INTERVAL_S = 2.0
_CSV_MAX_BUFFER_SIZE = 1000


def flush_all_csv_buffers():
    """
    Synchronously flushes all buffered CSV rows to their respective files.
    This function can be called to ensure data is written to disk before an operation.
    """
    with _CSV_BUFFER_LOCK:
        for filename, buffer in list(_CSV_BUFFERS.items()):
            if not buffer:
                continue

            temp_folder = _ensure_temp_folder()
            if not temp_folder:
                continue

            file_path = os.path.join(temp_folder, filename)
            # Check if file is empty or doesn't exist to decide whether to write headers
            write_headers = (
                not os.path.exists(file_path) or os.path.getsize(file_path) == 0
            )

            try:
                with open(file_path, "a", newline="", encoding="utf-8") as csvfile:
                    writer = csv.writer(csvfile)

                    # Extract all items from buffer to write in one go
                    rows_to_write = []
                    while buffer:
                        rows_to_write.append(buffer.popleft())

                    if not rows_to_write:
                        continue

                    # If headers need to be written, take them from the first buffered item
                    if write_headers:
                        _, headers = rows_to_write[0]
                        if headers:
                            writer.writerow(list(headers))

                    # Write all the rows
                    for row, _ in rows_to_write:
                        writer.writerow(list(row))
            except Exception:
                # On error, we can try to put items back in buffer or log the loss
                # For now, clear to prevent repeated failures on same data
                pass


def _csv_flush_worker():
    """Background thread that periodically flushes buffered CSV rows to disk."""
    while not _CSV_STOP_EVENT.wait(_CSV_FLUSH_INTERVAL_S):
        flush_all_csv_buffers()
    # Final flush on shutdown
    flush_all_csv_buffers()


def _ensure_csv_flush_thread():
    """Ensures the background CSV flush thread is running."""
    global _CSV_FLUSH_THREAD
    if _CSV_FLUSH_THREAD is None or not _CSV_FLUSH_THREAD.is_alive():
        _CSV_STOP_EVENT.clear()
        _CSV_FLUSH_THREAD = threading.Thread(
            target=_csv_flush_worker, daemon=True, name="CSVFlushThread"
        )
        _CSV_FLUSH_THREAD.start()
        atexit.register(_shutdown_csv_logger)


def _shutdown_csv_logger():
    """Signals the flush thread to stop and performs a final flush."""
    _CSV_STOP_EVENT.set()
    if _CSV_FLUSH_THREAD and _CSV_FLUSH_THREAD.is_alive():
        _CSV_FLUSH_THREAD.join(timeout=2.0)
    flush_all_csv_buffers()  # Final synchronous flush


def _ensure_temp_folder() -> Optional[str]:
    """Ensures the temporary directory exists and returns its path."""
    temp_folder = DEBUG_CONFIG.get("temp_folder_name", "Temp")
    try:
        os.makedirs(temp_folder, exist_ok=True)
        return temp_folder
    except Exception:
        return None


def append_row(
    filename: str, row: Iterable[Any], headers: Optional[Iterable[str]] = None
):
    """
    Appends a row to a CSV file buffer, to be written asynchronously.
    """
    if not DEBUG_CONFIG.get("enable_io_trace", True):
        return False

    _ensure_csv_flush_thread()

    with _CSV_BUFFER_LOCK:
        if filename not in _CSV_BUFFERS:
            _CSV_BUFFERS[filename] = deque()

        # Store row and headers together
        _CSV_BUFFERS[filename].append((row, headers))

        # Optional: trigger an early flush if buffer gets too large
        if len(_CSV_BUFFERS[filename]) >= _CSV_MAX_BUFFER_SIZE:
            threading.Thread(target=flush_all_csv_buffers, daemon=True).start()

    return True


def append_sent_position(
    timestamp: float, target_id: int, x: float, y: float, z: float, command: str
):
    """Logs a sent target position to the corresponding trace file."""
    filename = DEBUG_CONFIG.get("io_trace_sent_filename", "sent_positions.csv")
    headers = ["timestamp", "target_id", "x_ft", "y_ft", "z_ft", "command"]
    row = [timestamp, target_id, x, y, z, command]
    return append_row(filename, row, headers=headers)


def append_received_position(
    timestamp: float, target_id: int, x: float, y: float, z: float
):
    """Logs a received target position to the corresponding trace file."""
    filename = DEBUG_CONFIG.get("io_trace_received_filename", "received_positions.csv")
    headers = ["timestamp", "target_id", "x_ft", "y_ft", "z_ft"]
    row = [timestamp, target_id, x, y, z]
    return append_row(filename, row, headers=headers)
