"""Tests for main.py slideshow application."""
import os
import sys
import unittest
from unittest.mock import patch, MagicMock


class TestFlaskRoutes(unittest.TestCase):
    """Test the Flask web server routes."""

    def setUp(self):
        from main import flask_app
        self.flask_app = flask_app
        self.client = flask_app.test_client()

    def test_filename_returns_empty_when_no_app_instance(self):
        import main
        original = main._app_instance
        main._app_instance = None
        try:
            response = self.client.get('/filename')
            self.assertEqual(response.status_code, 200)
            self.assertEqual(response.data.decode(), "")
        finally:
            main._app_instance = original

    def test_filename_returns_curr_from_app_instance(self):
        import main
        original = main._app_instance
        mock_app = MagicMock()
        mock_app.curr = "/path/to/photo.jpg"
        main._app_instance = mock_app
        try:
            response = self.client.get('/filename')
            self.assertEqual(response.status_code, 200)
            self.assertEqual(response.data.decode(), "/path/to/photo.jpg")
        finally:
            main._app_instance = original

    def test_index_route(self):
        response = self.client.get('/')
        # Should return the web/index.html or 404 if not found in test env
        self.assertIn(response.status_code, [200, 404])


class TestGPSValueParsing(unittest.TestCase):
    """Test GPS value extraction logic extracted from add_location."""

    @staticmethod
    def get_gps_value(gps_data, key):
        """Extracted GPS parsing logic matching main.py implementation."""
        if key in gps_data:
            value = gps_data[key]
            if isinstance(value, tuple):
                if len(value) == 2:  # Rational format e.g. (34199, 2373)
                    return float(value[0]) / float(value[1])
                elif len(value) == 3:  # Standard DMS (degrees, minutes, seconds)
                    d = float(value[0][0]) / float(value[0][1])
                    m = float(value[1][0]) / float(value[1][1])
                    s = float(value[2][0]) / float(value[2][1])
                    return d + (m / 60.0) + (s / 3600.0)
                elif len(value) == 4:  # Special format (d_num, d_den, m_num, m_den)
                    d = float(value[0]) / float(value[1])
                    m = float(value[2]) / float(value[3])
                    return d + (m / 60.0)
            elif isinstance(value, int):
                return float(value)
            return float(value)
        return None

    def test_standard_dms_format(self):
        """Test standard degrees/minutes/seconds tuple format."""
        gps_data = {2: ((32, 1), (4, 1), (0, 1))}
        result = self.get_gps_value(gps_data, 2)
        expected = 32.0 + 4.0 / 60.0 + 0.0 / 3600.0
        self.assertAlmostEqual(result, expected)

    def test_rational_pair_format(self):
        """Test rational number pair format (numerator, denominator)."""
        gps_data = {2: (34199, 2373)}
        result = self.get_gps_value(gps_data, 2)
        self.assertAlmostEqual(result, 34199.0 / 2373.0)

    def test_four_element_format(self):
        """Test 4-element degrees+minutes format."""
        gps_data = {2: (32, 1, 30, 1)}
        result = self.get_gps_value(gps_data, 2)
        expected = 32.0 + 30.0 / 60.0
        self.assertAlmostEqual(result, expected)

    def test_integer_format(self):
        """Test plain integer GPS value."""
        gps_data = {5: 0}
        result = self.get_gps_value(gps_data, 5)
        self.assertEqual(result, 0.0)

    def test_missing_key_returns_none(self):
        """Test that missing key returns None."""
        gps_data = {2: ((32, 1), (4, 1), (0, 1))}
        result = self.get_gps_value(gps_data, 99)
        self.assertIsNone(result)

    def test_dms_with_nonzero_seconds(self):
        """Test DMS with actual seconds value."""
        gps_data = {2: ((32, 1), (4, 1), (30, 1))}
        result = self.get_gps_value(gps_data, 2)
        expected = 32.0 + 4.0 / 60.0 + 30.0 / 3600.0
        self.assertAlmostEqual(result, expected)

    def test_dms_with_fractional_values(self):
        """Test DMS with fractional degrees/minutes/seconds."""
        gps_data = {2: ((64, 2), (30, 1), (0, 1))}
        result = self.get_gps_value(gps_data, 2)
        expected = 32.0 + 30.0 / 60.0 + 0.0 / 3600.0
        self.assertAlmostEqual(result, expected)


class TestGPSRefParsing(unittest.TestCase):
    """Test GPS reference extraction."""

    @staticmethod
    def get_gps_ref(gps_data, key, default):
        """Extracted GPS ref logic matching main.py implementation."""
        if key in gps_data:
            value = gps_data[key]
            if isinstance(value, bytes):
                return value.decode('utf-8')
            return str(value)
        return default

    def test_bytes_ref(self):
        result = self.get_gps_ref({1: b'N'}, 1, 'N')
        self.assertEqual(result, 'N')

    def test_string_ref(self):
        result = self.get_gps_ref({1: 'S'}, 1, 'N')
        self.assertEqual(result, 'S')

    def test_missing_ref_returns_default(self):
        result = self.get_gps_ref({}, 1, 'N')
        self.assertEqual(result, 'N')

    def test_south_ref(self):
        result = self.get_gps_ref({1: b'S'}, 1, 'N')
        self.assertEqual(result, 'S')

    def test_west_ref(self):
        result = self.get_gps_ref({3: b'W'}, 3, 'E')
        self.assertEqual(result, 'W')


class TestHebrewRTL(unittest.TestCase):
    """Test Hebrew RTL text reversal logic."""

    @staticmethod
    def reverse_hebrew(location_text):
        """Extracted RTL reversal logic matching main.py implementation."""
        parts = location_text.split(', ')
        reversed_parts = [''.join(reversed(p)) for p in parts]
        return ' ,'.join(reversed_parts)

    def test_single_word(self):
        result = self.reverse_hebrew("שלום")
        self.assertEqual(result, "םולש")

    def test_two_parts_with_comma(self):
        result = self.reverse_hebrew("תל אביב, ישראל")
        # Each part reversed individually, joined with ' ,'
        self.assertEqual(result, "ביבא לת ,לארשי")

    def test_empty_string(self):
        result = self.reverse_hebrew("")
        self.assertEqual(result, "")

    def test_single_part_no_comma(self):
        result = self.reverse_hebrew("ירושלים")
        self.assertEqual(result, "םילשורי")

    def test_three_parts(self):
        result = self.reverse_hebrew("אב, גד, הו")
        self.assertEqual(result, "בא ,דג ,וה")


class TestImageFileDiscovery(unittest.TestCase):
    """Test image file discovery and validation."""

    @patch('main.sys')
    @patch('main.Path')
    def test_exits_when_no_images_found(self, mock_path_cls, mock_sys):
        """App should exit with error when no image files are found in root."""
        import main
        mock_sys.exit = MagicMock(side_effect=SystemExit(1))
        mock_path_cls.return_value.rglob.return_value = []

        with patch('main.argparse.ArgumentParser') as mock_parser_cls:
            mock_args = MagicMock()
            mock_args.root = '/empty/dir'
            mock_args.names = False
            mock_args.no_location = False
            mock_parser_cls.return_value.parse_args.return_value = mock_args

            with patch('main.os.path.isdir', return_value=True):
                with self.assertRaises(SystemExit):
                    # Re-run the main block logic
                    from pathlib import Path
                    root = mock_args.root
                    extensions = (".jpg", ".jpeg", ".heic")
                    image_files = []
                    for path in Path(root).rglob("*.*"):
                        for e in extensions:
                            if str(path).lower().endswith(e):
                                image_files.append(str(path))
                    if not image_files:
                        import sys as real_sys
                        print(f"No image files found in '{root}'. Supported formats: {', '.join(extensions)}")
                        real_sys.exit(1)

    def test_filters_by_extension(self):
        """Only .jpg, .jpeg, and .heic files should be included."""
        from pathlib import Path
        import tempfile, os

        with tempfile.TemporaryDirectory() as tmpdir:
            # Create test files
            for name in ["photo.jpg", "photo.jpeg", "photo.heic", "photo.png", "doc.txt", "PHOTO.JPG"]:
                Path(os.path.join(tmpdir, name)).touch()

            extensions = (".jpg", ".jpeg", ".heic")
            image_files = []
            for path in Path(tmpdir).rglob("*.*"):
                for e in extensions:
                    if str(path).lower().endswith(e):
                        image_files.append(str(path))

            self.assertEqual(len(image_files), 4)  # photo.jpg, photo.jpeg, photo.heic, PHOTO.JPG
            lower_files = [f.lower() for f in image_files]
            self.assertTrue(any(f.endswith('.jpg') for f in lower_files))
            self.assertTrue(any(f.endswith('.jpeg') for f in lower_files))
            self.assertTrue(any(f.endswith('.heic') for f in lower_files))
            self.assertFalse(any(f.endswith('.png') for f in lower_files))
            self.assertFalse(any(f.endswith('.txt') for f in lower_files))


class TestIndexWrapping(unittest.TestCase):
    """Test that image index wrapping works correctly with modulo."""

    def test_wrap_negative_one(self):
        """Index -1 with 10 pictures should wrap to 9."""
        self.assertEqual(-1 % 10, 9)

    def test_wrap_negative_two(self):
        """Index -2 with 10 pictures should wrap to 8."""
        self.assertEqual(-2 % 10, 8)

    def test_wrap_at_length(self):
        """Index equal to length should wrap to 0."""
        self.assertEqual(10 % 10, 0)

    def test_normal_index(self):
        """Normal index should remain unchanged."""
        self.assertEqual(5 % 10, 5)

    def test_zero_index(self):
        """Zero index should remain zero."""
        self.assertEqual(0 % 10, 0)

    def test_wrap_past_end(self):
        """Index past end should wrap correctly."""
        self.assertEqual(15 % 10, 5)


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