from __future__ import print_function
import unittest

from bayouradar_skywarn.bayou_radar import BayouRadarClient
from bayouradar_skywarn.config import configuration_warnings
from bayouradar_skywarn.models import Alert
from bayouradar_skywarn.source_result import FetchResult
from bayouradar_skywarn.sources import AlertSourceCoordinator


def alert(alert_id, event, area):
    return Alert(alert_id, {
        "event": event,
        "areaDesc": area,
        "status": "Actual",
        "messageType": "Alert",
    }, matched_areas=[area])


class FakeSource(object):
    def __init__(self, result):
        self.result = result
        self.calls = []

    def fetch(self, areas):
        self.calls.append(list(areas))
        return self.result


class BayouRadarClientTest(unittest.TestCase):
    def test_valid_empty_response_is_success(self):
        client = BayouRadarClient({"retries": 0})
        client._with_retries = lambda mode, params: {"type": "FeatureCollection", "features": []}
        result = client.fetch([{"code": "LAC019", "latitude": 30.2266, "longitude": -93.2174}])
        self.assertEqual(result.successful_areas, set(["LAC019"]))
        self.assertEqual(result.failed_areas, set())
        self.assertEqual(result.alerts, {})
        self.assertEqual(result.source_by_area["LAC019"], "bayou_radar")

    def test_flat_backend_alert_is_normalized(self):
        client = BayouRadarClient({"retries": 0})
        client._with_retries = lambda mode, params: {
            "ok": True,
            "alerts": [{
                "id": "urn:bayou:1",
                "event": "Tornado Warning",
                "area_desc": "Calcasieu Parish",
                "headline": "Tornado Warning for Calcasieu Parish",
                "severity": "Extreme",
                "urgency": "Immediate",
                "expires": "2099-01-01T00:00:00Z",
            }],
        }
        result = client.fetch([{"code": "LAC019", "bbox": {"west": -94, "south": 29.7, "east": -92.9, "north": 30.6}}])
        self.assertIn("urn:bayou:1", result.alerts)
        item = result.alerts["urn:bayou:1"]
        self.assertEqual(item.event, "Tornado Warning")
        self.assertEqual(item.matched_areas, ["LAC019"])

    def test_invalid_response_fails_primary_area(self):
        client = BayouRadarClient({"retries": 0})
        client._with_retries = lambda mode, params: {"ok": True, "service": "health"}
        result = client.fetch([{"code": "LAC019", "latitude": 30.2266, "longitude": -93.2174}])
        self.assertEqual(result.failed_areas, set(["LAC019"]))
        self.assertFalse(result.primary_healthy)


class ConfigurationWarningTest(unittest.TestCase):
    def test_missing_locator_warns_about_fallback(self):
        warnings = configuration_warnings({"areas": [{"code": "LAC019"}], "nws": {}, "bayou_radar": {}})
        self.assertTrue(any("NWS fallback" in warning for warning in warnings))

    def test_bbox_has_no_locator_warning(self):
        warnings = configuration_warnings({"areas": [{"code": "LAC019", "bbox": {"west": -94, "south": 30, "east": -92, "north": 31}}], "nws": {}, "bayou_radar": {}})
        self.assertEqual(warnings, [])


class CoordinatorTest(unittest.TestCase):
    def coordinator(self, primary_result, fallback_result, fallback_enabled=True):
        coordinator = AlertSourceCoordinator({
            "data_sources": {"fallback_enabled": fallback_enabled},
            "bayou_radar": {},
            "nws": {},
        })
        coordinator.primary = FakeSource(primary_result)
        coordinator.fallback = FakeSource(fallback_result)
        return coordinator

    def test_nws_not_called_for_authoritative_empty_bayou_response(self):
        primary = FetchResult()
        primary.successful_areas.add("LAC019")
        primary.source_by_area["LAC019"] = "bayou_radar"
        fallback = FetchResult()
        coordinator = self.coordinator(primary, fallback)
        final = coordinator.fetch([{"code": "LAC019"}])
        self.assertEqual(len(coordinator.fallback.calls), 0)
        self.assertEqual(final.successful_areas, set(["LAC019"]))
        self.assertEqual(final.fallback_areas, set())

    def test_failed_primary_area_uses_nws_fallback(self):
        primary = FetchResult()
        primary.failed_areas.add("LAC019")
        primary.primary_failed_areas.add("LAC019")
        primary.primary_healthy = False
        primary.errors.append("Bayou Radar unavailable")
        fallback = FetchResult()
        fallback.successful_areas.add("LAC019")
        fallback.add_alert(alert("urn:nws:1", "Tornado Warning", "LAC019"))
        coordinator = self.coordinator(primary, fallback)
        final = coordinator.fetch([{"code": "LAC019"}])
        self.assertEqual(coordinator.fallback.calls, [["LAC019"]])
        self.assertEqual(final.failed_areas, set())
        self.assertEqual(final.fallback_areas, set(["LAC019"]))
        self.assertEqual(final.source_by_area["LAC019"], "nws_fallback")
        self.assertIn("urn:nws:1", final.alerts)

    def test_partial_primary_failure_only_falls_back_failed_area(self):
        primary = FetchResult()
        primary.successful_areas.add("LAC019")
        primary.failed_areas.add("LAC023")
        primary.primary_failed_areas.add("LAC023")
        primary.primary_healthy = False
        primary.source_by_area["LAC019"] = "bayou_radar"
        primary.add_alert(alert("urn:bayou:1", "Flood Warning", "LAC019"))
        fallback = FetchResult()
        fallback.successful_areas.add("LAC023")
        fallback.add_alert(alert("urn:nws:2", "Tornado Watch", "LAC023"))
        coordinator = self.coordinator(primary, fallback)
        final = coordinator.fetch([{"code": "LAC019"}, {"code": "LAC023"}])
        self.assertEqual(coordinator.fallback.calls, [["LAC023"]])
        self.assertEqual(final.successful_areas, set(["LAC019", "LAC023"]))
        self.assertEqual(final.fallback_areas, set(["LAC023"]))
        self.assertEqual(final.source_by_area["LAC019"], "bayou_radar")
        self.assertEqual(final.source_by_area["LAC023"], "nws_fallback")
        self.assertEqual(set(final.alerts), set(["urn:bayou:1", "urn:nws:2"]))


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