# ABOUTME: Tests for ATP concentration extraction functions from assay descriptions
# ABOUTME: Validates regex patterns and unit conversion for kinase assay metadata

import unittest

import pandas as pd

from Capricho.chembl.atp_extraction import (
    parse_atp_from_description,
    convert_atp_units_to_um,
)


class TestParseAtpFromDescription(unittest.TestCase):
    """Tests for regex-based ATP extraction from assay descriptions."""

    def test_pattern_um_atp_prefix(self):
        """Test extraction of '10 µM ATP' pattern."""
        description = "Inhibition of GSK3-beta in presence of 10 µM ATP"
        result = parse_atp_from_description(description)
        self.assertEqual(result, 10.0)

    def test_pattern_um_atp_micrometer_symbol(self):
        """Test extraction of '10 uM ATP' pattern (ASCII µ)."""
        description = "Inhibition of kinase in presence of 10 uM ATP"
        result = parse_atp_from_description(description)
        self.assertEqual(result, 10.0)

    def test_pattern_atp_parentheses(self):
        """Test extraction of 'ATP (2 mM)' pattern."""
        description = "Substrate/Stimulus/Tracer: ATP (2 mM)"
        result = parse_atp_from_description(description)
        self.assertEqual(result, 2000.0)  # Converted from mM to µM

    def test_pattern_mM_atp(self):
        """Test extraction of '1 mM ATP' pattern and mM to µM conversion."""
        description = "Assay performed with 1 mM ATP substrate"
        result = parse_atp_from_description(description)
        self.assertEqual(result, 1000.0)

    def test_pattern_at_concentration(self):
        """Test extraction of 'at 2 uM ATP' pattern."""
        description = "Inhibition of EGFR at 2 uM ATP"
        result = parse_atp_from_description(description)
        self.assertEqual(result, 2.0)

    def test_pattern_presence_of(self):
        """Test extraction of 'in presence of 10 uM ATP' pattern."""
        description = "Inhibition of MSK1 in presence of 10 uM ATP"
        result = parse_atp_from_description(description)
        self.assertEqual(result, 10.0)

    def test_pattern_atp_equals(self):
        """Test extraction of 'ATP = 100 uM' pattern."""
        description = "IC50 determined with ATP = 100 uM"
        result = parse_atp_from_description(description)
        self.assertEqual(result, 100.0)

    def test_pattern_atp_colon(self):
        """Test extraction of 'ATP: 100 uM' pattern."""
        description = "Substrate concentration ATP: 100 uM"
        result = parse_atp_from_description(description)
        self.assertEqual(result, 100.0)

    def test_pattern_nM_conversion(self):
        """Test extraction and conversion from nM to µM."""
        description = "Inhibition measured at 500 nM ATP"
        result = parse_atp_from_description(description)
        self.assertEqual(result, 0.5)

    def test_decimal_concentration(self):
        """Test extraction of decimal ATP concentration."""
        description = "Inhibition of kinase with 0.5 mM ATP"
        result = parse_atp_from_description(description)
        self.assertEqual(result, 500.0)

    def test_no_atp_mentioned(self):
        """Test that None is returned when ATP is not mentioned."""
        description = "Inhibition of EGFR tyrosine kinase activity"
        result = parse_atp_from_description(description)
        self.assertIsNone(result)

    def test_atp_mentioned_without_concentration(self):
        """Test that None is returned when ATP is mentioned but no concentration."""
        description = "Inhibition of ATP binding site"
        result = parse_atp_from_description(description)
        self.assertIsNone(result)

    def test_gamma_32P_atp_pattern(self):
        """Test extraction of '[gamma-32P]ATP at 10 µM' pattern."""
        description = "Displacement of [gamma-32P]ATP at 10 µM from kinase"
        result = parse_atp_from_description(description)
        self.assertEqual(result, 10.0)

    def test_bracketed_atp_concentration(self):
        """Test extraction of '[ATP] = 10 µM' pattern."""
        description = "IC50 measured at [ATP] = 10 µM"
        result = parse_atp_from_description(description)
        self.assertEqual(result, 10.0)

    def test_case_insensitivity(self):
        """Test that extraction is case insensitive for ATP."""
        description = "Inhibition of kinase with 10 uM atp"
        result = parse_atp_from_description(description)
        self.assertEqual(result, 10.0)

    def test_empty_description(self):
        """Test handling of empty description."""
        result = parse_atp_from_description("")
        self.assertIsNone(result)

    def test_none_description(self):
        """Test handling of None description."""
        result = parse_atp_from_description(None)
        self.assertIsNone(result)


class TestConvertAtpUnitsToUm(unittest.TestCase):
    """Tests for ATP concentration unit conversion."""

    def test_um_no_conversion(self):
        """Test that µM values remain unchanged."""
        self.assertEqual(convert_atp_units_to_um(10.0, "µM"), 10.0)
        self.assertEqual(convert_atp_units_to_um(10.0, "uM"), 10.0)
        self.assertEqual(convert_atp_units_to_um(10.0, "μM"), 10.0)

    def test_mm_to_um(self):
        """Test conversion from mM to µM."""
        self.assertEqual(convert_atp_units_to_um(1.0, "mM"), 1000.0)
        self.assertEqual(convert_atp_units_to_um(0.5, "mM"), 500.0)

    def test_nm_to_um(self):
        """Test conversion from nM to µM."""
        self.assertEqual(convert_atp_units_to_um(1000.0, "nM"), 1.0)
        self.assertEqual(convert_atp_units_to_um(500.0, "nM"), 0.5)

    def test_m_to_um(self):
        """Test conversion from M to µM."""
        self.assertEqual(convert_atp_units_to_um(0.001, "M"), 1000.0)
        self.assertEqual(convert_atp_units_to_um(1e-6, "M"), 1.0)

    def test_invalid_unit_returns_none(self):
        """Test that invalid units return None."""
        result = convert_atp_units_to_um(10.0, "kg")
        self.assertIsNone(result)


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