import sys
from pathlib import Path
import unittest

ROOT = Path(__file__).resolve().parents[1]
SRC = ROOT / "src"

if str(SRC) not in sys.path:
    sys.path.insert(0, str(SRC))

from bpm_finder import (  # noqa: E402
    bpm_to_ms,
    estimate_bpm_from_taps,
    is_bpm_in_range,
    ms_to_bpm,
    normalize_bpm,
    tempo_bucket,
)


class ConversionTests(unittest.TestCase):
    def test_bpm_to_ms_for_quarter_notes(self) -> None:
        self.assertEqual(bpm_to_ms(128, note_division=4), 468.75)

    def test_ms_to_bpm_for_quarter_notes(self) -> None:
        self.assertEqual(ms_to_bpm(468.75, note_division=4), 128.0)


class TapTempoTests(unittest.TestCase):
    def test_estimate_bpm_from_taps_uses_median_strategy_by_default(self) -> None:
        timestamps = [0, 500, 1000, 1500, 2000]
        self.assertEqual(estimate_bpm_from_taps(timestamps), 120.0)

    def test_estimate_bpm_from_taps_rejects_unsorted_timestamps(self) -> None:
        with self.assertRaises(ValueError):
            estimate_bpm_from_taps([0, 500, 490])


class RangeTests(unittest.TestCase):
    def test_normalize_bpm_promotes_half_time_values(self) -> None:
        self.assertEqual(normalize_bpm(64), 128.0)

    def test_is_bpm_in_range_is_inclusive(self) -> None:
        self.assertTrue(is_bpm_in_range(128, 90, 180))
        self.assertFalse(is_bpm_in_range(181, 90, 180))

    def test_tempo_bucket_returns_expected_label(self) -> None:
        self.assertEqual(tempo_bucket(174), "fast")


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

