from unittest.mock import Mock, mock_open, patch

from django.test import SimpleTestCase

from routing.tasks import import_trig_data, regenerate_connections


class RegenerateConnectionsTest(SimpleTestCase):
    @patch("routing.tasks.SolarSystemConnection.objects.bulk_create")
    @patch("routing.tasks.SolarSystemConnection.objects.all")
    @patch("routing.tasks.Stargate.objects.all")
    def test_regenerate_connections_deletes_existing(self, mock_sg_all: Mock, mock_conn_all: Mock, _mock_bulk: Mock) -> None:
        mock_sg_all.return_value = []
        mock_conn_all.return_value.delete.return_value = None

        regenerate_connections()

        mock_conn_all.return_value.delete.assert_called_once()

    @patch("routing.tasks.Stargate.objects.all")
    def test_regenerate_connections_calls_bulk_create(self, mock_sg_all: Mock) -> None:
        sg = Mock()
        sg.solar_system = Mock()
        sg.destination = Mock(security_status=0.6)
        mock_sg_all.return_value = [sg]

        mock_conn_cls = Mock()
        mock_conn_cls.objects.all.return_value.delete.return_value = None
        mock_conn_cls.side_effect = lambda **kw: Mock(**kw)

        with patch("routing.tasks.SolarSystemConnection", mock_conn_cls):
            regenerate_connections()

        mock_conn_cls.objects.bulk_create.assert_called_once()


class RegenerateConnectionsWeightingTest(SimpleTestCase):
    """Verify the per-connection security weighting inside regenerate_connections"""

    def _run_with_stargates(self, security_statuses: list[float]) -> list:
        """Run regenerate_connections and capture the SolarSystemConnection objects created."""
        captured: list = []

        stargates = []
        for sec in security_statuses:
            sg = Mock()
            sg.solar_system = Mock()
            sg.destination = Mock(security_status=sec)
            stargates.append(sg)

        mock_conn_cls = Mock()
        mock_conn_cls.objects.all.return_value.delete.return_value = None

        def fake_conn_init(**kwargs: object) -> Mock:
            obj = Mock()
            for k, v in kwargs.items():
                setattr(obj, k, v)
            return obj

        mock_conn_cls.side_effect = fake_conn_init
        mock_conn_cls.objects.bulk_create.side_effect = lambda objs, **_kw: captured.extend(objs)

        with patch("routing.tasks.Stargate.objects.all", return_value=stargates), patch("routing.tasks.SolarSystemConnection", mock_conn_cls):
            regenerate_connections()

        return captured

    def test_high_sec_system_gets_low_p_safest(self) -> None:
        """Security >= 0.45 → p_safest=1, p_less_safe=50000"""
        objs = self._run_with_stargates([0.6])
        self.assertEqual(len(objs), 1)
        self.assertEqual(objs[0].p_safest, 1)
        self.assertEqual(objs[0].p_less_safe, 50000.0)

    def test_low_sec_system_gets_high_p_safest(self) -> None:
        """Security < 0.45 → p_safest=50000, p_less_safe=1"""
        objs = self._run_with_stargates([0.1])
        self.assertEqual(len(objs), 1)
        self.assertEqual(objs[0].p_safest, 50000.0)
        self.assertEqual(objs[0].p_less_safe, 1)

    def test_boundary_exactly_0_45_is_high_sec(self) -> None:
        """Security exactly 0.45 is treated as high-sec"""
        objs = self._run_with_stargates([0.45])
        self.assertEqual(objs[0].p_safest, 1)

    def test_multiple_stargates_produce_multiple_connections(self) -> None:
        objs = self._run_with_stargates([0.9, 0.1])
        self.assertEqual(len(objs), 2)

    def test_p_shortest_always_1(self) -> None:
        """p_shortest is always 1 regardless of security"""
        for sec in [0.0, 0.45, 1.0]:
            objs = self._run_with_stargates([sec])
            self.assertEqual(objs[0].p_shortest, 1)


class ImportTrigDataTest(SimpleTestCase):
    @patch("routing.tasks.TrigInvasion.objects.bulk_create")
    @patch("routing.tasks.csv.reader")
    @patch("builtins.open", new_callable=mock_open)
    def test_import_trig_data(self, _mock_file: Mock, mock_csv_reader: Mock, mock_bulk_create: Mock) -> None:
        mock_csv_reader.return_value = iter([
            ["system", "status"],
            ["30000142", "fortress"],
        ])

        import_trig_data()

        self.assertTrue(mock_bulk_create.called)


class ImportTrigDataFieldsTest(SimpleTestCase):
    @patch("routing.tasks.TrigInvasion.objects.bulk_create")
    @patch("routing.tasks.csv.reader")
    @patch("builtins.open", new_callable=mock_open)
    def test_trig_data_sets_system_id_and_status(self, _mock_file: Mock, mock_csv_reader: Mock, mock_bulk_create: Mock) -> None:
        mock_csv_reader.return_value = iter([
            ["system", "status"],
            ["30000142", "fortress"],
            ["30002187", "3"],
        ])

        import_trig_data()

        created = mock_bulk_create.call_args[0][0]
        self.assertEqual(len(created), 2)
        self.assertEqual(created[0].system_id, "30000142")
        self.assertEqual(created[0].status, "fortress")
        self.assertEqual(created[1].system_id, "30002187")
        self.assertEqual(created[1].status, "3")

    @patch("routing.tasks.TrigInvasion.objects.bulk_create")
    @patch("routing.tasks.csv.reader")
    @patch("builtins.open", new_callable=mock_open)
    def test_import_trig_data_skips_header(self, _mock_file: Mock, mock_csv_reader: Mock, mock_bulk_create: Mock) -> None:
        mock_csv_reader.return_value = iter([
            ["system", "status"],  # header only
        ])

        import_trig_data()

        created = mock_bulk_create.call_args[0][0]
        self.assertEqual(len(created), 0)


class DataImportIntegrationTest(SimpleTestCase):
    """Integration tests for data import tasks"""

    @patch("routing.tasks.SolarSystemConnection.objects.bulk_create")
    @patch("routing.tasks.SolarSystemConnection.objects.all")
    @patch("routing.tasks.Stargate.objects.all")
    def test_regenerate_connections_runs_without_error(self, mock_sg_all: Mock, mock_conn_all: Mock, _mock_bulk: Mock) -> None:
        """Verify regenerate_connections runs end-to-end without raising"""
        mock_sg_all.return_value = []
        mock_conn_all.return_value.delete.return_value = None

        regenerate_connections()  # must not raise

    @patch("builtins.open", new_callable=mock_open)
    def test_import_trig_data_reads_csv(self, mock_file: Mock) -> None:
        """Verify import_trig_data opens the CSV file"""
        with patch("routing.tasks.csv.reader", return_value=iter([])):
            with patch("routing.tasks.TrigInvasion.objects.bulk_create"):
                try:
                    import_trig_data()
                except (StopIteration, ValueError):
                    pass  # Expected from mocking

    def test_weighting_logic_boundary_at_0_45(self) -> None:
        """Verify security status weighting boundary is at 0.45"""
        high_sec_status = 0.45
        low_sec_status = 0.44

        high_sec_safest = 1 if high_sec_status >= 0.45 else 50000.0
        low_sec_safest = 1 if low_sec_status >= 0.45 else 50000.0

        self.assertEqual(high_sec_safest, 1)
        self.assertEqual(low_sec_safest, 50000.0)
