from __future__ import print_function
import os
import shutil
import tempfile
import unittest
import yaml
from bayouradar_skywarn.config import load_config
from bayouradar_skywarn.engine import Engine
from bayouradar_skywarn.models import Alert
from bayouradar_skywarn.util import isoformat


class EngineTest(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.mkdtemp(prefix="brs-test-")
        self.engines = []
        self.config_path = os.path.join(self.tmp, "config.yaml")
        config = {
            "app": {"poll_seconds": 30, "startup_quiet": False, "state_db": os.path.join(self.tmp, "state.db"), "runtime_dir": os.path.join(self.tmp, "run")},
            "nws": {"areas": [{"code": "LAC019", "name": "Calcasieu Parish"}]},
            "asterisk": {"enabled": False, "nodes": []},
            "audio": {"enabled": False},
            "web": {"enabled": False},
            "supermon": {"enabled": True, "warnings_path": os.path.join(self.tmp, "warnings.txt")},
            "tail_message": {"enabled": True, "path": os.path.join(self.tmp, "wx-tail")},
            "notifications": {"pushover": {"enabled": False}},
            "testing": {"inject": True, "alerts": [{"event": "Tornado Warning", "areas": ["LAC019"]}]},
        }
        with open(self.config_path, "w") as handle:
            yaml.safe_dump(config, handle)

    def tearDown(self):
        for engine in self.engines:
            engine.close()
        shutil.rmtree(self.tmp)

    def engine(self):
        engine = Engine(load_config(self.config_path))
        self.engines.append(engine)
        return engine

    def test_injected_alert_and_clear(self):
        engine = self.engine()
        first = engine.cycle()
        self.assertEqual(first["active"], 1)
        self.assertEqual(first["added"], 1)
        self.assertTrue(os.path.exists(os.path.join(self.tmp, "wx-tail.txt")))
        engine.config["testing"]["alerts"] = []
        second = engine.cycle()
        self.assertEqual(second["active"], 0)
        self.assertEqual(second["cleared"], 1)
        self.assertFalse(os.path.exists(os.path.join(self.tmp, "wx-tail.txt")))

    def test_multiple_same_event_instances_are_preserved(self):
        engine = self.engine()
        engine.config["testing"]["alerts"] = [
            {"event": "Tornado Warning", "area_desc": "Western Calcasieu Parish", "areas": ["LAC019"]},
            {"event": "Tornado Warning", "area_desc": "Eastern Calcasieu Parish", "areas": ["LAC019"]},
        ]
        result = engine.cycle()
        self.assertEqual(result["active"], 2)
        self.assertEqual(len(engine.state.get_alerts()), 2)

    def test_cap_reference_is_update_not_clear_plus_new(self):
        engine = self.engine()
        old = Alert("urn:old", {"event": "Severe Thunderstorm Warning", "effective": isoformat(), "status": "Actual"}, matched_areas=["LAC019"])
        new = Alert("urn:new", {"event": "Severe Thunderstorm Warning", "effective": isoformat(), "status": "Actual", "references": [{"@id": "urn:old"}]}, matched_areas=["LAC019"])
        added, updated, cleared = engine._diff({old.id: old}, {new.id: new})
        self.assertEqual(len(added), 0)
        self.assertEqual(len(updated), 1)
        self.assertEqual(len(cleared), 0)

    def test_partial_outage_retains_cached_alert_for_failed_area(self):
        engine = self.engine()
        first = engine.cycle()
        self.assertEqual(first["active"], 1)
        cached = engine.state.get_alerts()
        engine.fetch_alerts = lambda: ({}, set(), set(["LAC019"]), ["simulated outage"])
        degraded = engine.cycle()
        self.assertEqual(degraded["status"], "degraded")
        self.assertEqual(degraded["active"], 1)
        self.assertEqual(set(engine.state.get_alerts().keys()), set(cached.keys()))

    def test_unchanged_cycle_does_not_replace_alert_rows(self):
        engine = self.engine()
        first = engine.cycle()
        self.assertTrue(first["changed"])
        second = engine.cycle()
        self.assertFalse(second["changed"])
        self.assertEqual(second["added"], 0)
        self.assertEqual(second["updated"], 0)

    def test_status_identifies_testing_source(self):
        engine = self.engine()
        result = engine.cycle()
        self.assertEqual(result["data_source"], "testing")
        status_path = os.path.join(self.tmp, "run", "status.json")
        with open(status_path, "r") as handle:
            import json
            status = json.load(handle)
        self.assertEqual(status["primary_source"], "bayou_radar")
        self.assertEqual(status["current_source"], "testing")
        self.assertEqual(status["fallback_areas"], [])

    def test_disabled_mode_removes_stale_public_outputs(self):
        engine = self.engine()
        engine.cycle()
        tail = os.path.join(self.tmp, "wx-tail.txt")
        warnings = os.path.join(self.tmp, "warnings.txt")
        self.assertTrue(os.path.exists(tail))
        self.assertTrue(os.path.getsize(warnings) > 0)
        engine.set_enabled(False)
        result = engine.cycle()
        self.assertEqual(result["status"], "disabled")
        self.assertFalse(os.path.exists(tail))
        with open(warnings, "r") as handle:
            self.assertEqual(handle.read(), "")


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