"""Core temperature logger.

This is adapted from the original `pc_realtime_logger.py`, but refactored to be
importable as a library module.

(c) 2024-26 Prof. Flavio ABREU ARAUJO. All rights reserved.
"""

from __future__ import annotations

import contextlib
import csv
import threading
import time
from collections import deque
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path

from neng_scpi_tools.myserial import SCPISerial


@dataclass
class LoggerConfig:
    port: str | None = None
    output_file: Path | None = None
    max_points: int = 101
    avg_level: int = 8


class TemperatureLogger:
    """Real-time temperature logger.

    Contract:
      - Connects to the instrument via SCPI over serial
      - Starts a background thread to continuously read *one sample* at a time
      - Buffers the most recent values in bounded deques
      - Writes CSV rows as data is acquired

    The GUI pulls data from the public deques under `data_lock`.
    """

    def __init__(self, config: LoggerConfig) -> None:
        self.config = config

        if self.config.output_file is None:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            self.config.output_file = Path(f"tmp117_log_{timestamp}.csv")

        self.timestamps = deque(maxlen=self.config.max_points)
        self.temperatures_1 = deque(maxlen=self.config.max_points)
        self.temperatures_2 = deque(maxlen=self.config.max_points)
        self.skew_ms_data = deque(maxlen=self.config.max_points)
        self.diff_data = deque(maxlen=self.config.max_points)

        self.start_time: float | None = None
        self.point_count = 0

        self.data_lock = threading.Lock()
        self._running = False
        self._thread: threading.Thread | None = None

        self._instr: SCPISerial | None = None
        self._csv_fp = None
        self._csv_writer: csv.writer | None = None

        # runtime flags detected during connect
        self.is_dual_sensor = False
        self.sensor_count = 1

    @property
    def running(self) -> bool:
        return self._running

    def connect(self) -> None:
        self._instr = SCPISerial(port=self.config.port)

        # Basic ID query (best-effort)
        with contextlib.suppress(Exception):
            _ = self._instr.query("*IDN?")

        # Note: Averaging level is passed as parameter to SENS:SYNC? query
        # e.g., SENS:SYNC? 8 for 8 averages. Stored for use in _loop()
        # Valid values: 1, 8, 32, 64

        # Detect dual sensor mode (best-effort)
        try:
            resp = self._instr.query("SENS:COUNT?")
            n = int(resp.strip())
            self.sensor_count = max(1, n)
            self.is_dual_sensor = self.sensor_count >= 2
        except Exception:
            self.sensor_count = 1
            self.is_dual_sensor = False

        # Open CSV
        out = self.config.output_file
        assert out is not None
        self._csv_fp = open(out, "w", newline="", encoding="utf-8")  # noqa: SIM115
        self._csv_writer = csv.writer(self._csv_fp)

        if self.is_dual_sensor:
            self._csv_writer.writerow(
                ["Elapsed_Time_s", "Timestamp", "T1_C", "T2_C", "Skew_ms", "Delta_T_C"]
            )
        else:
            self._csv_writer.writerow(["Elapsed_Time_s", "Timestamp", "T1_C"])
        self._csv_fp.flush()

    def close(self) -> None:
        self.stop()
        if self._instr is not None:
            self._instr.close()
            self._instr = None
        if self._csv_fp is not None:
            try:
                self._csv_fp.close()
            finally:
                self._csv_fp = None
                self._csv_writer = None

    def start(self) -> None:
        if self._running:
            return
        if self._instr is None:
            self.connect()

        self._running = True
        self.start_time = time.time()
        self._thread = threading.Thread(target=self._loop, name="temp-logger", daemon=True)
        self._thread.start()

    def stop(self) -> None:
        self._running = False
        if self._thread is not None:
            self._thread.join(timeout=2.0)
            self._thread = None

    def _loop(self) -> None:
        assert self._instr is not None
        assert self._csv_writer is not None
        assert self.start_time is not None

        while self._running:
            ts = datetime.now().isoformat(timespec="microseconds")
            elapsed = time.time() - self.start_time

            try:
                if self.is_dual_sensor:
                    # Use SENS:SYNC? with configured averaging level
                    # Response format: timestamp,T1,T2,skew_ms,DIFF
                    resp = self._instr.query(f"SENS:SYNC? {self.config.avg_level}").strip()
                    parts = resp.split(",")
                    if len(parts) >= 5:
                        # parts: [device_ts, T1, T2, skew_ms, diff]
                        t1 = float(parts[1])
                        t2 = float(parts[2])
                        skew_ms = float(parts[3])
                        dt = float(parts[4])
                    else:
                        # Fallback if format is different
                        t1 = float(parts[0]) if len(parts) > 0 else 0.0
                        t2 = float(parts[1]) if len(parts) > 1 else 0.0
                        skew_ms = 0.0
                        dt = t1 - t2

                    with self.data_lock:
                        self.timestamps.append(elapsed)
                        self.temperatures_1.append(t1)
                        self.temperatures_2.append(t2)
                        self.skew_ms_data.append(skew_ms)
                        self.diff_data.append(dt)
                        self.point_count += 1

                    self._csv_writer.writerow(
                        [
                            f"{elapsed:.6f}",
                            ts,
                            f"{t1:.6f}",
                            f"{t2:.6f}",
                            f"{skew_ms:.3f}",
                            f"{dt:.6f}",
                        ]
                    )
                else:
                    # Single sensor: use SENS:TEMP?
                    t1 = float(self._instr.query("SENS:TEMP?").strip().split(",")[-1])
                    with self.data_lock:
                        self.timestamps.append(elapsed)
                        self.temperatures_1.append(t1)
                        self.point_count += 1
                    self._csv_writer.writerow([f"{elapsed:.6f}", ts, f"{t1:.6f}"])

                if self._csv_fp is not None:
                    self._csv_fp.flush()

            except Exception:
                # Keep looping; GUI will show errors if desired.
                time.sleep(0.05)
                continue

            # Small delay to prevent tight loop if device answers too fast
            time.sleep(0.01)
