import logging
import math
import unittest
from unittest.mock import patch

import pytest

# Configure logging to avoid pollution during tests
from pylabrobot.plate_reading.tecan.spark20m.spark_processor import (
  process_absorbance,
  process_fluorescence,
)

logging.basicConfig(level=logging.CRITICAL)


class TestProcessAbsorbance(unittest.TestCase):
  def test_process_success(self) -> None:
    # Mock _parse_raw_data to return a controlled structure
    # We need a reference sequence (grouped) and a measurement sequence (standalone)

    # Reference Data Setup
    # Block 0: Dark
    # avg_rd_dark = 100
    # avg_md_dark = 50
    dark_pairs = [
      {"U16RD_DARK_0": 100, "U16MD_DARK_1": 50},
      {"U16RD_DARK_0": 100, "U16MD_DARK_1": 50},
    ]

    # Block 1: Reference
    # avg_rd_ref = 1000
    # avg_md_ref = 500
    ref_pairs = [{"U16RD_0": 1000, "U16MD_1": 500}, {"U16RD_0": 1000, "U16MD_1": 500}]

    # Calculations for Reference:
    # ref_md_dark = 500 - 50 = 450
    # ref_rd_dark = 1000 - 100 = 900
    # ref_ratio_h9 = 450 / 900 = 0.5

    parsed_data = {
      "SEQ_REF": [
        {"type": "grouped", "blocks": [{"rd_md_pairs": dark_pairs}, {"rd_md_pairs": ref_pairs}]}
      ],
      "SEQ_MEAS": [
        {
          "type": "standalone",
          "block": {
            "measurements": [
              {
                # Measurement 1
                # Sample 1
                # sample_md = 250, sample_rd = 2000
                # sample_md_dark = 250 - 50 = 200
                # sample_rd_dark = 2000 - 100 = 1900
                # sample_ratio = 200 / 1900 = 0.105263...
                # ratio_final = sample_ratio / ref_ratio_h9 = (2/19) / 0.5 = 4/19 = 0.210526...
                # h9 = -log10(0.210526...)
                "inner_loops": [{"U16MD_1": 250, "U16RD_0": 2000}]
              }
            ]
          },
        }
      ],
    }

    with patch(
      "pylabrobot.plate_reading.tecan.spark20m.spark_processor._parse_raw_data",
      return_value=parsed_data,
    ):
      results = process_absorbance([])

    self.assertEqual(len(results), 1)
    self.assertEqual(len(results[0]), 1)

    expected_ratio = (200 / 1900) / 0.5
    expected_h9 = -math.log10(expected_ratio)

    assert isinstance(results[0][0], float)
    self.assertAlmostEqual(results[0][0], expected_h9, places=5)

  def test_process_missing_reference(self) -> None:
    # Only standalone sequences, no grouped reference
    parsed_data = {"SEQ_MEAS": [{"type": "standalone", "block": {"measurements": []}}]}
    with patch(
      "pylabrobot.plate_reading.tecan.spark20m.spark_processor._parse_raw_data",
      return_value=parsed_data,
    ):
      results = process_absorbance([])

    self.assertEqual(results, [])

  def test_process_empty_data(self) -> None:
    with patch(
      "pylabrobot.plate_reading.tecan.spark20m.spark_processor._parse_raw_data", return_value={}
    ):
      results = process_absorbance([])
    self.assertEqual(results, [])

  def test_zero_division_protection(self) -> None:
    # Setup data that would cause division by zero if not handled
    # E.g. ref_rd_dark = 0 => avg_rd_ref = avg_rd_dark

    dark_pairs = [{"U16RD_DARK_0": 100, "U16MD_DARK_1": 50}]
    ref_pairs = [{"U16RD_0": 100, "U16MD_1": 500}]  # avg_rd_ref = 100

    parsed_data = {
      "SEQ_REF": [
        {"type": "grouped", "blocks": [{"rd_md_pairs": dark_pairs}, {"rd_md_pairs": ref_pairs}]}
      ],
      "SEQ_MEAS": [
        {
          "type": "standalone",
          "block": {"measurements": [{"inner_loops": [{"U16MD_1": 250, "U16RD_0": 2000}]}]},
        }
      ],
    }

    with patch(
      "pylabrobot.plate_reading.tecan.spark20m.spark_processor._parse_raw_data",
      return_value=parsed_data,
    ):
      results = process_absorbance([])

    # Should result in nan or handled gracefully
    # If ref_ratio_h9 is NaN (due to 0 division), then final ratio is NaN, so h9 is nan
    self.assertTrue(math.isnan(results[0][0]))

  def test_process_real_data(self) -> None:
    abs = [
      b"\x88\t\x00\x01\x13\x93",
      b"\x83\t\x00\x02\x00\x02\x8a",
      b"\x88\t\x00\x06\x0f\x0c\x1a\x12\x15\x16\x8f",
      b"\x83\t\x00\t\x01\x00\x00\x07\x7f\x00\x1e\x00\x1e\xfa",
      b"\x83\t\x00P\x02'\x01\x0e\x02\x1d\x01\x0e\x02\x1d\x01\x0f\x02$\x01\x0e\x02 \x01\x10\x02'\x01\x0f\x02!\x01\x0f\x02\"\x01\x0e\x02 \x01\x0f\x02\x1a\x01\x0e\x02!\x01\x10\x02\x18\x01\x0f\x02!\x01\x0f\x02\x1e\x01\x0f\x02\x1d\x01\x0e\x02\x17\x01\x0f\x02\x1d\x01\x0f\x02\x1f\x01\x10\x02\x1e\x01\x0e\x02$\x01\x10\xd2",
      b"\x83\t\x00(\x02%\x01\x0f\x02\x1f\x01\x0f\x02\x1f\x01\x0e\x02 \x01\x0e\x02\x1f\x01\x11\x02\x1b\x01\x0e\x02\x1d\x01\x0f\x02\x1f\x01\x0f\x02\x1e\x01\x0f\x02&\x01\x0f\x86",
      b"\x88\t\x00\x08\x0f\x0c\x11\x18\x1a\x12\x00\x02\x89",
      b"\x83\t\x00\r\x01\x00\x00\t\xce\x00/\x00\x17\x00\x1e\x00\x1ey",
      b"\x83\t\x00P\x9fI\xa2\x13\xa06\xa3\x03\xa0\x07\xa2\xe2\xa0\x1d\xa2\xf6\xa0 \xa2\xe0\xa0z\xa3J\xa0\xa4\xa3y\x9f\x94\xa2c\xa0\xff\xa3\xce\xa2]\xa5.\xa2\x7f\xa5^\xa3#\xa6\x04\xa4A\xa7+\xa4\xeb\xa7\xd1\xa3W\xa60\xa2\xac\xa5|\xa1\xa2\xa4t\xa3-\xa6\x06\xa2\xf5\xa5\xcc\xa1E\xa4)m",
      b"\x83\t\x00(\xa0\x07\xa2\xef\xa0\xf7\xa3\xc1\xa08\xa3\x06\xa0m\xa37\xa2\x93\xa5a\xa2\xce\xa5\xb3\xa3i\xa6I\xa3W\xa6/\xa3\xa1\xa6\x84\xa0\xcf\xa3\xaa\x88",
      b"\x88\n\x00\x06\x12\x0f\x0c\x12\x00\x02\x85",
      b"\x83\n\x00\t\x00\x0c\x01\x00\x00\x11U\x00\n\xc3",
      b"\x83\n\x00(\xa0\x11\x01/\x9e\xef\x01.\xa0$\x01.\xa0x\x01/\xa0K\x01/\xa0\xc1\x010\xa0\x1c\x01/\xa1}\x010\xa1\x9c\x01/\xa2\xe5\x010\xb2",
      b"\x83\n\x00\x07\x01\x00\x00\x13p\x00\n\xe6",
      b"\x83\n\x00(\xa25\x92\x16\xa1\x9d\x91\x95\xa3`\x93\x1c\xa2\xc1\x92\x98\xa4\x1d\x93\xce\xa4\xb4\x94h\xa3\xbc\x93|\xa5\xec\x95q\xa5)\x94\xcb\xa7\xa9\x97\x08\xb8",
      b"\x83\n\x00\x07\x01\x00\x00\x15S\x00\n\xc3",
      b"\x83\n\x00(\xa4M\x8fk\xa4\xbc\x8f\xd2\xa4\xc4\x8f\xe9\xa2B\x8d\xa9\xa4n\x8f\x8f\xa4~\x8f\x8e\xa5\x88\x90z\xa5\xc2\x90\xb6\xa6\x8e\x91x\xa9\xea\x94h\xc2",
      b"\x83\n\x00\x07\x01\x00\x00\x17\x0c\x00\n\x9e",
      b"\x83\n\x00(\xa1\xc9\x8bU\xa0j\x8a$\xa3 \x8cV\xa1\xc5\x8b(\xa4\xbe\x8d\xd2\xa3\xe3\x8d'\xa4\x8f\x8d\xd3\xa4\xd7\x8e\x1d\xa4\xb4\x8d\xed\xa5\x96\x8e\x99\x83",
      b"\x83\n\x00\x07\x01\x00\x00\x18\xa8\x00\n5",
      b"\x83\n\x00(\xa2\x84\x90\x90\xa2\xab\x90\xaa\xa1|\x8f\x9c\xa3\x9f\x91l\xa4\xb1\x92r\xa3>\x912\xa36\x916\xa7y\x95\t\xa7\xb3\x953\xa6Y\x94\x03\xda",
      b"\x83\n\x00\x07\x01\x00\x00\x1aD\x00\n\xdb",
      b"\x83\n\x00(\xa2\x1b\x8bp\xa2\xc7\x8b\xf8\xa4X\x8dT\xa4Z\x8dh\xa3I\x8c\x9c\xa3Y\x8c\xb3\xa4\xbb\x8d\xbf\xa6\x15\x8e\xd5\xa6\xd3\x8fq\xa7l\x90\x12\xf3",
      b"\x83\n\x00\x07\x01\x00\x00\x1b\xe0\x00\n~",
      b"\x83\n\x00(\xa2\xf2\x903\xa3\x18\x90S\xa2\xcb\x90\x0e\xa3\x8a\x90\xb4\xa2\xea\x90,\xa4\x13\x91F\xa6\xcc\x93\xa9\xa7\xf4\x94\xb7\xa6!\x93\x1d\xa6\x1c\x93\tM",
      b"\x83\n\x00\x07\x01\x00\x00\x1d\xc3\x00\n[",
      b"\x83\n\x00(\xa2c\x88\r\xa2\xb3\x88y\xa2$\x87\xf3\xa3\x16\x88\xb9\xa4/\x89\x97\xa4\x0f\x89\x85\xa5\xb5\x8a\xf7\xa5k\x8a\xb9\xa98\x8d\xe1\xa5\xe0\x8b\x01\xe3",
      b"\x83\n\x00\x07\x01\x00\x00\x1fz\x00\n\xe0",
      b"\x83\n\x00(\xa2\xb4\x8f/\xa3i\x8f\xcc\xa4v\x90\xa9\xa4&\x90A\xa3\x05\x8f3\xa6\x07\x91\xfb\xa5\xef\x91\xe3\xa5\x0c\x91!\xa7[\x93)\xa6\xc0\x92\x9f\xfb",
      b"\x83\n\x00\x07\x01\x00\x00 \xf6\x00\nS",
      b"\x83\n\x00(\xa2\xed\x91T\xa3\x98\x92\x00\xa2I\x90\xc5\xa3$\x91\x9a\xa2\xb9\x914\xa4N\x92\xa0\xa6e\x94\x88\xa6O\x94{\xa7\x07\x95\x1e\xa5)\x93vM",
      b'\x83\n\x00\x07\x01\x00\x00"\xab\x00\n\x0c',
      b"\x83\n\x00(\xa3\x13\x8e\xee\xa0\x7f\x8c\xa0\xa1O\x8d^\xa5\x08\x90\x9e\xa1\x81\x8d\x94\xa5\x03\x90\xab\xa6\x1e\x91\xa2\xa5D\x90\xe6\xa7S\x92\xae\xa6G\x91\xc9\xd5",
      b"\x83\n\x00\x07\x01\x00\x00$\x90\x00\n1",
      b"\x83\n\x00(\xa0\xe7\x90\xd5\xa1n\x91J\xa31\x92\xe0\xa5t\x94\xe0\xa3\xd1\x93{\xa3\xdb\x93\x8b\xa5\xc5\x95>\xa5O\x94\xd8\xa7\x07\x96s\xa7}\x96\xd7\xbb",
      b"\x88\x0b\x00\x06\x12\x0f\x0c\x12\x00\x02\x84",
      b"\x83\x0b\x00\t\x00\x0c\x01\x00\x00(\xba\x00\n\x14",
      b"\x83\x0b\x00(\x9f\xc7\x01$\x9f\xd9\x01&\xa0\xb2\x01%\xa2\xb8\x01'\xa0\xbc\x01%\xa2\xad\x01%\xa26\x01%\xa1\xca\x01%\xa2C\x01&\xa4|\x01&c",
      b"\x83\x0b\x00\x07\x01\x00\x00*\r\x00\n\xa3",
      b"\x83\x0b\x00(\x9e\xd1\x01&\x9f\xf8\x01&\xa2\x07\x01)\xa1\t\x01)\xa1\x94\x01'\xa1\xcb\x01#\xa2X\x01$\xa3\xce\x01%\xa5\x10\x01'\xa5\xc3\x01'\x9b",
      b"\x83\x0b\x00\x07\x01\x00\x00+\xa5\x00\n\n",
      b"\x83\x0b\x00(\xa0\xa5\x01'\xa2\x8a\x01%\xa0<\x01'\xa0\xc8\x01(\xa0l\x01(\xa13\x01'\xa2\xf2\x01'\xa3\xd2\x01&\xa4\x85\x01&\xa4\xfb\x01'z",
      b"\x83\x0b\x00\x07\x01\x00\x00-\x86\x00\n/",
      b"\x83\x0b\x00(\xa0f\x01&\xa0Y\x01&\xa0\xea\x01'\xa2\x08\x01&\x9f\xa4\x01&\xa3\x95\x01'\xa2\xa7\x01'\xa5\r\x01(\xa5\xb6\x01'\xa4\x8b\x01'\xec",
      b"\x83\x0b\x00\x07\x01\x00\x00/=\x00\n\x96",
      b"\x83\x0b\x00(\x9f\xe3\x01&\xa0k\x01$\x9e\xfd\x01%\xa1\xac\x01'\x9e\xf8\x01'\xa2g\x01&\xa6G\x01&\xa4\xa6\x01%\xa2\xb4\x01%\xa3\xf4\x01&y",
      b"\x83\x0b\x00\x07\x01\x00\x000\xd5\x00\na",
      b"\x83\x0b\x00(\xa0L\x01%\xa0\xc0\x01$\x9f\x01\x01$\xa1\xac\x01#\xa2\x1a\x01#\xa1\x93\x01&\xa3\x88\x01%\xa4\xbb\x01&\xa5\xf7\x01&\xa4]\x01$\xa8",
      b"\x83\x0b\x00\x07\x01\x00\x002o\x00\n\xd9",
      b"\x83\x0b\x00(\xa1Y\x01$\xa0#\x01#\xa0\xad\x01%\xa0K\x01%\xa1\xa3\x01'\xa2\xbb\x01&\xa6\xad\x01'\xa5\x00\x01%\xa6\x89\x01%\xa2\xf7\x01$\xf7",
      b"\x83\x0b\x00\x07\x01\x00\x004R\x00\n\xe2",
      b"\x83\x0b\x00(\xa0&\x01$\xa1(\x01#\xa1\x1f\x01#\xa2o\x01%\xa2\r\x01%\xa4\x0f\x01$\xa3\xe5\x01%\xa5,\x01$\xa5z\x01&\xa5\x0f\x01&c",
      b"\x83\x0b\x00\x07\x01\x00\x006\n\x00\n\xb8",
      b"\x83\x0b\x00(\xa2\x07\x91\xab\xa5\r\x94_\xa4;\x93\xa8\xa5\x8d\x94\xc9\xa4&\x93\x96\xa5:\x94\x8f\xa8C\x97Y\xa7\x8c\x96\xb8\xa5,\x94\x86\xa4\xb1\x94\x1c\xa8",
      b"\x83\x0b\x00\x07\x01\x00\x007\xa4\x00\n\x17",
      b"\x83\x0b\x00(\xa2\xec\x92\xec\xa4,\x94\x14\xa3)\x93.\xa3p\x93_\xa1\x9d\x91\xce\xa5\x15\x94\xde\xa4\xe7\x94\xba\xa6\xd0\x96n\xa4\x11\x93\xf4\xa4\x92\x94a\xdb",
      b"\x83\x0b\x00\x07\x01\x00\x009<\x00\n\x81",
      b"\x83\x0b\x00(\xa3\x05\x92\xaf\xa2A\x92\x17\xa2\xa9\x92i\xa2/\x91\xf5\xa4?\x93\xd4\xa4\xe0\x94l\xa5\x10\x94\x98\xa6\xe6\x96B\xa6\x9a\x95\xe5\xa6;\x95\x9a\xd7",
      b"\x83\x0b\x00\x07\x01\x00\x00:\xd4\x00\nj",
      b"\x83\x0b\x00(\xa2:\x91\xe1\xa1\x9e\x91[\xa1t\x91'\xa1\xc7\x91y\xa3o\x93\x08\xa4V\x93\xdc\xa7%\x96k\xa5q\x94\xe9\xa6\x07\x95f\xa3\xe5\x93\x8df",
    ]
    proc = process_absorbance(abs)
    res = [
      [
        3.11063277753661,
        0.05336257720528929,
        0.06702104600211652,
        0.07299936992281636,
        0.05887417419669415,
        0.0734290764001552,
        0.061013133423609846,
        0.08478106298852653,
        0.06390239296863666,
        0.057396722793976014,
        0.06527130757756598,
        0.05359379934802616,
      ],
      [
        3.268966569235883,
        3.2532244522041207,
        3.243948508296082,
        3.2467027122313525,
        3.263830898919421,
        3.286531371816557,
        3.277737476157588,
        3.290353848437903,
        0.054148473423301896,
        0.052819168592559695,
        0.05369339271038759,
        0.05389217987680245,
      ],
    ]
    assert len(proc) == len(res)
    for proc_row, res_row in zip(proc, res):
      assert proc_row == pytest.approx(res_row)


class TestProcessFluorescence(unittest.TestCase):
  def test_process_success(self) -> None:
    # Calibration Sequence (Grouped, count=2)
    # Block 0: Dark
    # signalDark = 50, refDark = 100
    dark_block = {
      "header_types": ["U16RD_DARK", "U16MD_DARK"],  # Mimic header check
      "rd_md_pairs": [{"U16MD_DARK_1": 50, "U16RD_DARK_0": 100}],
    }
    # Block 1: Bright
    # refBright = 50000
    bright_block = {"rd_md_pairs": [{"U16RD_0": 50000}]}

    # K Calculation:
    # K = (50000 - 100) / (65536 - 50) * 65536
    # K = 49900 / 65486 * 65536 ~= 0.76199... * 65536 ~= 49938
    k_val = (50000 - 100) / (65536.0 - 50) * 65536.0

    # Measurement Sequence
    # rawSignal = 20000, rawRefSignal = 30000
    # rfu = (20000 - 50) / (30000 - 100) * K
    # rfu = 19950 / 29900 * K ~= 0.6672 * K
    meas_block = {
      "structure_type": "nested_mult",
      "measurements": [{"inner_loops": [{"U16MD_1": 20000, "U16RD_0": 30000}]}],
    }

    parsed_data = {
      "SEQ_CAL": [{"type": "grouped", "count": 2, "blocks": [dark_block, bright_block]}],
      "SEQ_MEAS": [{"type": "standalone", "block": meas_block}],
    }

    with patch(
      "pylabrobot.plate_reading.tecan.spark20m.spark_processor._parse_raw_data",
      return_value=parsed_data,
    ):
      results = process_fluorescence([])

    self.assertEqual(len(results), 1)
    self.assertEqual(len(results[0]), 1)

    expected_rfu = (20000 - 50) / (30000 - 100) * k_val
    assert isinstance(results[0][0], float)
    self.assertAlmostEqual(results[0][0], expected_rfu, places=3)

  def test_process_missing_calibration(self) -> None:
    parsed_data = {
      "SEQ_MEAS": [
        {"type": "standalone", "block": {"structure_type": "nested_mult", "measurements": []}}
      ]
    }
    with patch(
      "pylabrobot.plate_reading.tecan.spark20m.spark_processor._parse_raw_data",
      return_value=parsed_data,
    ):
      results = process_fluorescence([])
    self.assertEqual(results, [])

  def test_process_invalid_dark_block(self) -> None:
    # Missing 'DARK' in header_types
    dark_block = {
      "header_types": ["U16RD", "U16MD"],
      "rd_md_pairs": [{"U16MD_1": 50, "U16RD_0": 100}],
    }
    bright_block = {"rd_md_pairs": [{"U16RD_0": 50000}]}

    parsed_data = {
      "SEQ_CAL": [{"type": "grouped", "count": 2, "blocks": [dark_block, bright_block]}]
    }

    with patch(
      "pylabrobot.plate_reading.tecan.spark20m.spark_processor._parse_raw_data",
      return_value=parsed_data,
    ):
      results = process_fluorescence([])

    # Should return empty list because Block 0 does not look like Dark calibration
    self.assertEqual(results, [])

  def test_process_real_data(self) -> None:
    fluo = [
      b"\x88\x0f\x00\x01\x13\x95",
      b"\x83\x0f\x00\x02\x00\x02\x8c",
      b"\x88\x0f\x00\x07\x0f\x0c\x0b\x1a\x12\x15\x16\x83",
      b"\x83\x0f\x00\x0b\x01\x00\x00\x0b.\x12\xf2\x00Z\x00ZC",
      b"\x83\x0f\x00\x04\x00r\rf\x91",
      b"\x83\x0f\x01d\x00q\rc\x00r\rd\x00r\rr\x00q\rk\x00q\rd\x00q\rg\x00r\rd\x00q\re\x00q\rd\x00q\rn\x00q\re\x00q\rk\x00r\rd\x00q\re\x00r\rg\x00q\re\x00q\re\x00r\re\x00r\rd\x00q\re\x00q\rd\x00r\rd\x00q\rc\x00r\rd\x00r\rc\x00q\ro\x00q\rd\x00q\rc\x00r\re\x00r\re\x00q\rh\x00r\re\x00p\rf\x00q\re\x00q\rc\x00r\re\x00r\rf\x00q\rh\x00p\rd\x00r\rd\x00q\rg\x00r\rd\x00r\rd\x00q\rc\x00q\rd\x00r\re\x00r\re\x00q\rl\x00r\rd\x00r\rf\x00q\rc\x00r\rc\x00q\rf\x00q\re\x00q\rd\x00r\re\x00q\rd\x00q\rd\x00q\re\x00q\re\x00p\re\x00q\rc\x00p\rd\x00q\rf\x00r\rd\x00q\rf\x00r\re\x00r\re\x00s\rd\x00r\rd\x00q\rd\x00q\rd\x00r\re\x00r\rd\x00q\rd\x00q\rp\x00q\re\x00r\re\x00q\rf\x00q\rg\x00q\rf\x00r\rc\x00q\rd\x00p\rq\x00r\re\x00r\rd\x00q\re\x00q\rc\x00q\rd\xeb",
      b"\x88\x0f\x00\x07\x0f\x0c\x0b\x11\x1a\x12\x00\x91",
      b"\x83\x0f\x00\r\x01\x00\x00\x15+\x12\xf2\x00\x07\x00Z\x00ZY",
      b"\x83\x0f\x00\x02\xa5<\x17",
      b"\x83\x0f\x00\xb2\xa9k\xa8=\xa7\xdb\xa80\xa8\xeb\xa89\xa9\x97\xa8\x1c\xa9\x82\xa8\xc9\xa9\xcb\xa98\xa9\x06\xaaJ\xa9\xcf\xa9\xca\xa9\xd2\xa7\xd6\xa8X\xaa\x9b\xa9N\xaa\x1c\xaad\xaab\xa9\xb3\xac\xca\xab\x03\xaa'\xac\xaf\xaa\x99\xaa\xee\xa9\xf4\xa9\xe3\xa9~\xa9\xec\xab\xa5\xaa1\xaa\x81\xa8\x81\xa9\xe5\xa9\xa2\xa9\xa9\xa8\xa7\xa9-\xa9k\xa8\x7f\xaap\xa9\x0f\xaa\xc3\xa9\xd3\xab\x05\xab\x08\xaap\xaa\x83\xaaZ\xabU\xa9[\xaa\xba\xa9x\xaaN\xaa\xc8\xa9\xca\xa9\xc7\xaa\x97\xa8\x9e\xab\x1e\xaa`\xaa\x95\xaa,\xacG\xa9G\xab\x91\xab\xbb\xab*\xaaJ\xaa\x9b\xabU\xab\xec\xaak\xa5\xbd\xac\xd1\xaa\xe1\xa9y\xaai\xa9A\xaa_\xaao\xa8.\xa9\xc6\xbd",
      b"\x88\x10\x00\x08\x0f\x0b\x17\x12\x0c\x12\x00\x02\x8d",
      b"\x83\x10\x00\r\x01\x12\xf2\x14\xe6\x00\x0c\x00\x00\x1fN\x00\x1e\xce",
      b"\x83\x10\x00x\xa5w\xba\x86\xa8l\xc0Z\xa8\x05\xba\xc8\xa7\xd3\xc0\xc5\xa8-\xbf/\xa8H\xbc\x9f\xa8[\xb9\xb1\xa87\xbdi\xa8n\xbcE\xa8\xee\xbe\x1f\xa9\x91\xc1\xae\xa9\x14\xbe\xd7\xa8\r\xbe(\xa8\xa5\xc0\x03\xa8l\xbb\x8c\xa9\x8c\xc0*\xa7\xbf\xber\xaa\x00\xbc\x02\xaa,\xbd|\xa9\x93\xbcQ\xaa\xd0\xbcD\xa9\x0f\xc1\x83\xa8\x81\xbe\x11\xa9]\xbb\xa8\xa9-\xbbB\xa7}\xc1\x93\xa9x\xba\xb2\xabg\xc0E\xa8u\xba\xef\xa96\xbey\xeb",
      b"\x83\x10\x00\x06\x00\x00#\x99\x00\x1e1",
      b"\x83\x10\x00x\xa7\x16\xbd\xee\xa9\x19\xc1\xe2\xa7\xd6\xbd\xe5\xa8\xc0\xbd\xc1\xa7\x9f\xbeR\xa8\xbd\xc0\x92\xa7C\xbf\x96\xa9E\xbe\x9d\xa9\x1e\xbe\xf5\xa9h\xbc\x12\xa8\x8f\xbb\xcd\xa7w\xbe\xf4\xaa\x10\xc1^\xa7t\xbbs\xa9\x1c\xba\xe2\xaa\x8a\xbf\xf7\xa9\x95\xbf\x9d\xa9\x99\xbb\xd1\xa9\xa1\xbe\xda\xa9\x8c\xb7.\xaaT\xbc\x88\xaa\x12\xbaI\xa9A\xbeB\xa9\x81\xbd3\xa9\xb9\xba\xf8\xa9\xba\xc1\n\xaa6\xbf\\\xa9\x1b\xc1\xe3\xaa\xb7\xbc\xa5\xa7\xb8\xc0t\xb8",
      b"\x83\x10\x00\x06\x00\x00%\x9b\x00\x1e5",
      b"\x83\x10\x00x\xa7$\xba\x8e\xa8\xf9\xb7\x8a\xa8\x01\xbd\xb7\xaa!\xb6\xf7\xa9%\xb58\xa7\x15\xb9\x1a\xaa^\xb7\xc4\xa7\xea\xbc\xa6\xa9\xa2\xb9\xfb\xa9\xcb\xb7\x0e\xaa\x19\xb9a\xab\x11\xb9\xb3\xa9\x8e\xb9\xf5\xa9\r\xb6\xfe\xa9\xd5\xb8\xff\xaa\t\xb8E\xa9h\xbb\xb9\xab\xdf\xbd|\xa9M\xb8\xfc\xaaQ\xb8\x84\xa9\n\xb9G\xa9U\xbb\x18\xaa\x81\xc1\xa0\xab\x15\xb7\xdb\xaa\xdd\xb8X\xaar\xba\xd3\xa8\xb0\xbb5\xa9,\xb7;\xaa\x9d\xb9\xe2\xabO\xbe\xaa\x9c",
      b"\x83\x10\x00\x06\x00\x00(\x05\x00\x1e\xa6",
      b"\x83\x10\x00x\xa6\x8a\xb5\xdc\xa8\xeb\xb3>\xa9\xb7\xb7\xfc\xa8\x14\xb6;\xa8z\xb7\x1d\xa8\xe9\xba\xe0\xa8\xaf\xb7\x84\xa9)\xb5a\xa8\x15\xb9\xa6\xab)\xb4\x84\xa9h\xb5\xa1\xa9\x9c\xb3\x04\xa7\xe2\xb4,\xa8\xbd\xb5\xd0\xaa\x07\xb71\xa9r\xb5t\xa9\x83\xb5\xae\xa8\x1f\xb4\xe7\xa8}\xb3k\xab\xa2\xb5\x86\xa7F\xae\x0e\xa7\x02\xb5\xb1\xa9f\xb4\xb7\xab\xe2\xb6\x8b\xaa<\xb66\xaa\x0f\xb8!\xa8\x89\xb9T\xaa\xd0\xb4\xd8\xab\x00\xb3\\\xa9\x89\xb3\x8e\xcf",
      b"\x83\x10\x00\x06\x00\x00*\x07\x00\x1e\xa6",
      b"\x83\x10\x00x\xa7\x18\xbf\xe0\xaa^\xbf`\xa9`\xbfs\xa8\x0f\xbbL\xa8v\xb9h\xa9.\xb9\x11\xa6V\xbe\xa7\xa7C\xbcN\xa7\xf9\xbc|\xa9\x15\xbe\x0e\xa98\xba\x13\xa9\x95\xbe\x9d\xaa\x16\xbb\xde\xaav\xbe \xa8\x18\xba\xea\xaa3\xbb\x92\xa8\x7f\xbe\xce\xa9\x15\xba\xb0\xa9\xd3\xba\x06\xa9\x11\xbc\xa9\xaa\xdd\xc3\x04\xa8\xc3\xbc\xde\xa6\x8c\xb9\xf6\xa9\xc3\xbc<\xa90\xbd\xa3\xaa\xfc\xbe\x8f\xaa\x1e\xbd\xdf\xa9\xd7\xbe\x84\xaa\xc8\xbcs\xa8G\xbc\xac\xb8",
      b"\x83\x10\x00\x06\x00\x00,\x06\x00\x1e\xa1",
      b"\x83\x10\x00x\xa7\xb3\xbcV\xa9\x1c\xbf\xbb\xa7K\xbdq\xa9\x9e\xbc`\xa8X\xbaw\xa7\x12\xb8\xe5\xa9f\xc0\xb3\xa7\x1e\xbcx\xaa!\xbcR\xaa\xa2\xbb\x15\xaa\x01\xbd\xd3\xa7\xd1\xbai\xa9(\xb8\x8d\xa9\xf2\xbd\r\xa9\x91\xb7b\xaa\x15\xbf\xa6\xa7\x04\xc2\xea\xaaP\xbc|\xa9\x9c\xbb^\xa8\x01\xbf/\xabD\xba\xbe\xaa\xaf\xbaS\xa8\x8e\xbd\x0b\xa8\x95\xba?\xa9\xc7\xb9t\xa9v\xb8_\xaa+\xbad\xa9H\xbbM\xa9\xdb\xb8\x92\xa9z\xbaJ\x13",
      b"\x83\x10\x00\x06\x00\x00.\x06\x00\x1e\xa3",
      b"\x83\x10\x00x\xa5\x1b\xbd\x9b\xa7\xf9\xbc\x19\xa9k\xbc\xba\xaa\x92\xbe\xc7\xa8P\xbfj\xa8\xc4\xc0\x92\xa8\xbf\xbb_\xa9\xc4\xbb\xa1\xa9\xa0\xbf\xcf\xa8\xdf\xc3L\xa9\x92\xbf\xe3\xa9H\xbc}\xa9@\xbc\x1e\xa8k\xbe\xe5\xa9\xf4\xbf\xcb\xa8\xf3\xbc\x95\xa9\x89\xbd\x05\xa9U\xbd\xa1\xaac\xbcS\xa7k\xbc$\xa9\xee\xbc\x85\xa6\xec\xbd\r\xa7\x83\xbag\xa8R\xba\xc9\xa9\xe3\xbb\xee\xa7\x8b\xbeP\xa8w\xba.\xaa\x02\xbe\r\xa9O\xbc\x90\xa9\xef\xbb\x08\x98",
      b"\x83\x10\x00\x06\x00\x000t\x00\x1e\xcf",
      b"\x83\x10\x00x\xa5\x90\xc1\x0f\xa8\x9a\xc0o\xa8C\xc2\x12\xa6\xa7\xc2<\xa7\x88\xc2\xa7\xaa.\xc2\x18\xa7a\xc0\x00\xa9]\xc0\x85\xa8\xb7\xc0\x0c\xa9\xef\xc2\xe5\xa8\xf1\xc2_\xa8^\xc2\x1a\xa9\x89\xc0\xee\xac\x0f\xc2%\xa9*\xc2\x0f\xa9\x03\xc2}\xaa\xab\xc5 \xa9\xbf\xc3\t\xa8\xd1\xc3\xcc\xa9\xd7\xc7\x06\xa9c\xc3W\xaa\x14\xc1\x99\xaa\x8b\xc3K\xa7V\xc1r\xa8\xb3\xc3\xab\xa8\x1a\xc0\xb6\xaa\x07\xc2\x94\xaa2\xba\xf2\xa9\xe0\xc2\xf0\xaa@\xc5\x81H",
      b"\x83\x10\x00\x06\x00\x002t\x00\x1e\xcd",
      b"\x83\x10\x00x\xa4\xaa\xba\x07\xa9C\xba\xfc\xa7\\\xbb\xff\xa8|\xbb\x9a\xa8\x82\xbc\x0c\xa9\x99\xb7\xa4\xa9\x97\xbe\xd4\xa9\xef\xba\x9d\xa6\xed\xbc\xec\xa9\xd5\xba\x8d\xa8v\xb9\x08\xa9\x98\xb9\x07\xa85\xb9\xaf\xa8\xb6\xbe\xbf\xa7\x0c\xba\x85\xa8q\xc2n\xa8\x10\xbe\xbf\xa8\x19\xba\xa9\xa8R\xbc\xa2\xa8\x1e\xb9\x80\xa8\x1c\xbc\xbf\xaa@\xbc\xdc\xabi\xbb\xf3\xa9\x8f\xbb\x8e\xa9\x0f\xbcM\xa8\xb0\xc1\xca\xaa:\xbc*\xa9\xa5\xbai\xa8\xcb\xbd\x03\xa9\x00\xc0BH",
      b"\x83\x10\x00\x06\x00\x004t\x00\x1e\xcb",
      b"\x83\x10\x00x\xa4\xe9\xb9\x16\xa6\xca\xbeb\xa8\x88\xc0^\xa8\xb5\xbbo\xa8\x9d\xbb\x97\xa5\xe5\xc12\xa8\x1b\xbb\xe1\xa8\x7f\xbcG\xa7\xc2\xba\xea\xa9s\xc1|\xa72\xc0\xf0\xa9\x05\xbfJ\xa8r\xc0m\xa7\xca\xbd\xf5\xa9d\xbe\xda\xab^\xbc\xf9\xaa\x0e\xbd\x14\xa9\x82\xbe\x9a\xaa\x81\xc0\xb1\xa8\x8f\xbb\xe1\xa8\xe6\xbf\xae\xa8\xdf\xc0\x86\xa9\xa2\xc1 \xa8\xa3\xbet\xab@\xc12\xaaL\xc1'\xa7\xfe\xbc\x9e\xa9\x92\xbe\xd0\xa7\x82\xbd\xcd\xa9;\xbe\xc5\xae",
      b"\x83\x10\x00\x06\x00\x006\xde\x00\x1ec",
      b"\x83\x10\x00x\xa7\x9f\xb9<\xa8\x08\xbc\xa2\xa8\x00\xbe\xfa\xa8\xa5\xbe\xa6\xa8\xcf\xb7\x9a\xa8\x9c\xbb\xac\xa8\x8f\xbb\xd8\xa8\x91\xc0\xb4\xa7\x0f\xbe\x8f\xa8\xdf\xc0\xfc\xa7o\xbb\xce\xa8\x08\xbe\x0c\xa9 \xbey\xa9\x7f\xc0]\xa7\x0c\xbc\xad\xa9\xeb\xbfr\xa7y\xba5\xa9\x92\xbcW\xa8Q\xbe\xf3\xab\xce\xc3\x96\xa9\xc1\xbe\x1a\xaa\x91\xbcG\xa8\x82\xbdR\xaa\xf6\xc0\xf1\xa7\xdc\xbd\xd3\xa8\x17\xbe\xf5\xac\xa7\xbc\xfa\xaa(\xb9\xfe\xaal\xc12\xaa\x17\xc1Y\xe4",
      b"\x83\x10\x00\x06\x00\x008\xdf\x00\x1el",
      b"\x83\x10\x00x\xa3\x7f\xc10\xa8W\xcb\xb5\xa6\r\xc4\x1b\xa8T\xc6}\xa6\xeb\xc7\xb3\xa9\x0f\xccF\xa7\xec\xc6\x90\xa8\x02\xc9\xc2\xa9=\xc5\xa7\xaa\xa4\xc7\x9b\xa9d\xc1\x17\xa9\x91\xc8}\xa8\xfd\xc9\xed\xa7\xaf\xc6\xc1\xa8\x95\xcb\xf4\xa8E\xc7O\xa9\x8a\xc5:\xa8\xc6\xcc\xa6\xab \xca,\xa9C\xc5\x96\xa8\xef\xc7\xf0\xa9\x0c\xca\x9d\xa83\xc4Z\xa9V\xc2\x00\xa9\x86\xc6O\xaa&\xc94\xa9\x9f\xcb\x98\xa9j\xc6;\xa8\xb6\xc7\xe6\xa9\x81\xc99+",
    ]
    proc = process_fluorescence(fluo)
    res = [
      [
        47948.62782562279,
        47952.18275926122,
        46652.99592416698,
        45646.78062435356,
        47650.381479622505,
        47338.837493347826,
        47808.66757608314,
        49076.496551486445,
        47532.80952833949,
        48178.720264450945,
        47931.65850286039,
        50658.76437083986,
      ]
    ]
    assert len(proc) == len(res)
    for proc_row, res_row in zip(proc, res):
      assert proc_row == pytest.approx(res_row)


if __name__ == "__main__":
  unittest.main()
