"""
Synchronous HTTP client for the lf-spectroscopy service (port 8002 by default).

All methods are blocking and intended to be called from a QThread worker.
"""
from __future__ import annotations

import os

from src.clients import extract_error_text


class SpectroscopyError(Exception):
    def __init__(self, message: str, status_code: int | None = None) -> None:
        super().__init__(message)
        self.status_code = status_code


class SpectroscopyClient:
    """Thin wrapper around the lf-spectroscopy REST API."""

    def __init__(self, base_url: str = "http://localhost:8002") -> None:
        self.base_url = base_url.rstrip("/")

    # -- internals ---------------------------------------------------------

    def _get(self, path: str) -> dict:
        import httpx
        try:
            r = httpx.get(
                f"{self.base_url}{path}",
                timeout=httpx.Timeout(connect=3.0, read=120.0, write=10.0, pool=5.0),
            )
        except httpx.ConnectError as exc:
            raise SpectroscopyError(
                f"Cannot connect to spectroscopy service at {self.base_url}"
            ) from exc
        except httpx.TimeoutException as exc:
            raise SpectroscopyError("Spectroscopy request timed out") from exc
        if not r.is_success:
            raise SpectroscopyError(extract_error_text(r), status_code=r.status_code)
        return r.json()

    def _post_form_file(self, path: str, file_path: str, fields: dict) -> dict:
        """POST multipart/form-data with a file and extra form fields."""
        import httpx
        filename = os.path.basename(file_path)
        try:
            with open(file_path, "rb") as fh:
                r = httpx.post(
                    f"{self.base_url}{path}",
                    data=fields,
                    files={"file": (filename, fh, "application/octet-stream")},
                    timeout=httpx.Timeout(connect=3.0, read=300.0, write=60.0, pool=5.0),
                )
        except httpx.ConnectError as exc:
            raise SpectroscopyError(
                f"Cannot connect to spectroscopy service at {self.base_url}"
            ) from exc
        except httpx.TimeoutException as exc:
            raise SpectroscopyError("Spectroscopy upload timed out") from exc
        if not r.is_success:
            raise SpectroscopyError(extract_error_text(r), status_code=r.status_code)
        return r.json()

    def _post_form(self, path: str, fields: dict) -> dict:
        """POST multipart/form-data (no file)."""
        import httpx
        try:
            r = httpx.post(
                f"{self.base_url}{path}",
                data=fields,
                timeout=httpx.Timeout(connect=3.0, read=300.0, write=10.0, pool=5.0),
            )
        except httpx.ConnectError as exc:
            raise SpectroscopyError(
                f"Cannot connect to spectroscopy service at {self.base_url}"
            ) from exc
        except httpx.TimeoutException as exc:
            raise SpectroscopyError("Spectroscopy request timed out") from exc
        if not r.is_success:
            raise SpectroscopyError(extract_error_text(r), status_code=r.status_code)
        return r.json()

    # -- public API --------------------------------------------------------

    def healthcheck(self) -> bool:
        """Return True if the service responds on /health."""
        try:
            self._get("/health")
            return True
        except SpectroscopyError:
            return False

    def analyze(
        self,
        json_file_path: str,
        *,
        center_freq:   float = 2.95e6,
        bandpass_bw:   float = 0.1e6,
        lp_cutoff:     float = 0.6e6,
        dec_factor:    int   = 10,
        zero_pad:      int   = 500_001,
        butter_order:  int   = 4,
        voltage_range: float = 5.0,
        averaging_num: int   = 0,
        data_num:      int   = 0,
        channel_num:   int   = 1,
    ) -> dict:
        """
        Upload a hardware JSON file and run full NMR analysis.

        Returns a dict with keys:
          metadata, raw_time, demod_time, spectrum, metrics
        """
        fields = {
            "center_freq":   str(center_freq),
            "bandpass_bw":   str(bandpass_bw),
            "lp_cutoff":     str(lp_cutoff),
            "dec_factor":    str(dec_factor),
            "zero_pad":      str(zero_pad),
            "butter_order":  str(butter_order),
            "voltage_range": str(voltage_range),
            "averaging_num": str(averaging_num),
            "data_num":      str(data_num),
            "channel_num":   str(channel_num),
        }
        return self._post_form_file("/analyze/", json_file_path, fields)

    def analyze_from_params(self, json_file_path: str, params: dict) -> dict:
        """
        Convenience wrapper: pass params as a plain dict (matching the
        keys of NMRParams) instead of keyword arguments.

        Recognised keys: center_freq, bandpass_bw, lp_cutoff, dec_factor,
        zero_pad, butter_order, voltage_range, averaging_num, data_num, channel_num.
        """
        return self.analyze(json_file_path, **{
            k: params[k] for k in (
                "center_freq", "bandpass_bw", "lp_cutoff", "dec_factor",
                "zero_pad", "butter_order", "voltage_range",
                "averaging_num", "data_num", "channel_num",
            ) if k in params
        })

    def submit_batch(
        self,
        folder_path: str,
        *,
        center_freq:   float = 2.95e6,
        bandpass_bw:   float = 0.1e6,
        lp_cutoff:     float = 0.6e6,
        dec_factor:    int   = 10,
        zero_pad:      int   = 500_001,
        butter_order:  int   = 4,
        voltage_range: float = 5.0,
        averaging_num: int   = 0,
        data_num:      int   = 0,
        channel_num:   int   = 1,
    ) -> str:
        """
        Start batch NMR analysis for all *.json files in folder_path.
        Returns batch_id (str).
        """
        fields = {
            "folder_path":   folder_path,
            "center_freq":   str(center_freq),
            "bandpass_bw":   str(bandpass_bw),
            "lp_cutoff":     str(lp_cutoff),
            "dec_factor":    str(dec_factor),
            "zero_pad":      str(zero_pad),
            "butter_order":  str(butter_order),
            "voltage_range": str(voltage_range),
            "averaging_num": str(averaging_num),
            "data_num":      str(data_num),
            "channel_num":   str(channel_num),
        }
        data = self._post_form("/batch/", fields)
        return data["batch_id"]

    def submit_batch_from_params(self, folder_path: str, params: dict) -> str:
        """Convenience wrapper using params dict (see submit_batch)."""
        return self.submit_batch(folder_path, **{
            k: params[k] for k in (
                "center_freq", "bandpass_bw", "lp_cutoff", "dec_factor",
                "zero_pad", "butter_order", "voltage_range",
                "averaging_num", "data_num", "channel_num",
            ) if k in params
        })

    def get_batch(self, batch_id: str) -> dict:
        """
        Poll batch analysis status and results.

        Returns a dict with keys:
          status       - "processing" | "completed"
          progress     - {done: int, total: int}
          results      - list of per-file dicts
          summary      - {mean_peak_freq_khz, std_peak_freq_khz, mean_fwhm_khz, n_ok, n_error}
        """
        return self._get(f"/batch/{batch_id}")
