"""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 .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)
