"""
test_logger_setup.py

Covers the switch from a single ever-growing, size-rotated sip_bot.log
to one dated file per trading day (logs/sip_bot_YYYY-MM-DD.log), plus
retention pruning of old daily files.

Run:
    python -m pytest tests/test_logger_setup.py -v
"""

import sys
import os
import glob
import shutil
import tempfile
import unittest
from datetime import date, timedelta
from unittest.mock import patch

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import logger_setup  # noqa: E402


class FakeEtDatetime:
    """Stand-in for market_time.now_et()'s return value -- only .date()
    is exercised by logger_setup, so that's all this needs to support."""
    def __init__(self, d: date):
        self._d = d

    def date(self):
        return self._d


class TestDailyFileHandler(unittest.TestCase):
    def setUp(self):
        self.tmpdir = tempfile.mkdtemp()
        self.log_dir = __import__("pathlib").Path(self.tmpdir)

    def tearDown(self):
        shutil.rmtree(self.tmpdir, ignore_errors=True)

    def _make_record(self, msg="hello"):
        import logging
        return logging.LogRecord(
            name="sip_bot.test", level=logging.INFO, pathname=__file__,
            lineno=1, msg=msg, args=(), exc_info=None,
        )

    def test_active_file_is_named_with_todays_date(self):
        today = date(2026, 8, 19)
        with patch.object(logger_setup.market_time, "now_et", return_value=FakeEtDatetime(today)):
            handler = logger_setup._DailyFileHandler(self.log_dir, "sip_bot")
            handler.setFormatter(None)
            handler.emit(self._make_record("first message"))

        expected_path = self.log_dir / "sip_bot_2026-08-19.log"
        self.assertTrue(expected_path.exists(),
                         f"expected {expected_path} to exist, found: {list(self.log_dir.iterdir())}")
        with open(expected_path) as f:
            self.assertIn("first message", f.read())

    def test_same_day_writes_append_to_the_same_file(self):
        today = date(2026, 8, 19)
        with patch.object(logger_setup.market_time, "now_et", return_value=FakeEtDatetime(today)):
            handler = logger_setup._DailyFileHandler(self.log_dir, "sip_bot")
            handler.setFormatter(None)
            handler.emit(self._make_record("message one"))
            handler.emit(self._make_record("message two"))

        matching_files = glob.glob(str(self.log_dir / "sip_bot_2026-08-19.log"))
        self.assertEqual(len(matching_files), 1, "should still be exactly one file for the day")
        with open(matching_files[0]) as f:
            content = f.read()
        self.assertIn("message one", content)
        self.assertIn("message two", content)

    def test_crossing_midnight_opens_a_new_dated_file(self):
        day1 = date(2026, 8, 19)
        day2 = date(2026, 8, 20)

        handler = logger_setup._DailyFileHandler(self.log_dir, "sip_bot")
        handler.setFormatter(None)

        with patch.object(logger_setup.market_time, "now_et", return_value=FakeEtDatetime(day1)):
            handler.emit(self._make_record("day one message"))

        with patch.object(logger_setup.market_time, "now_et", return_value=FakeEtDatetime(day2)):
            handler.emit(self._make_record("day two message"))

        day1_path = self.log_dir / "sip_bot_2026-08-19.log"
        day2_path = self.log_dir / "sip_bot_2026-08-20.log"
        self.assertTrue(day1_path.exists())
        self.assertTrue(day2_path.exists())

        with open(day1_path) as f:
            self.assertIn("day one message", f.read())
            self.assertNotIn("day two message", f.read())
        with open(day2_path) as f:
            self.assertIn("day two message", f.read())
            self.assertNotIn("day one message", f.read())

    def test_no_stale_sip_bot_log_file_is_created(self):
        """The old behavior wrote to a single logs/sip_bot.log -- confirm
        that filename is never touched by the new handler."""
        today = date(2026, 8, 19)
        with patch.object(logger_setup.market_time, "now_et", return_value=FakeEtDatetime(today)):
            handler = logger_setup._DailyFileHandler(self.log_dir, "sip_bot")
            handler.setFormatter(None)
            handler.emit(self._make_record("hello"))

        self.assertFalse((self.log_dir / "sip_bot.log").exists())


class TestPruneOldLogs(unittest.TestCase):
    def setUp(self):
        self.tmpdir = tempfile.mkdtemp()
        self.log_dir = __import__("pathlib").Path(self.tmpdir)

    def tearDown(self):
        shutil.rmtree(self.tmpdir, ignore_errors=True)

    def _touch(self, filename):
        (self.log_dir / filename).write_text("log content\n")

    def test_prunes_files_older_than_retention_days(self):
        today = date(2026, 8, 20)
        old_date = today - timedelta(days=45)   # older than 30-day retention
        recent_date = today - timedelta(days=5)  # within retention

        self._touch(f"sip_bot_{old_date.isoformat()}.log")
        self._touch(f"sip_bot_{recent_date.isoformat()}.log")
        self._touch(f"sip_bot_{today.isoformat()}.log")
        self._touch("not_a_dated_file.log")  # should never be touched

        with patch.object(logger_setup.market_time, "now_et", return_value=FakeEtDatetime(today)):
            logger_setup._prune_old_logs(self.log_dir, "sip_bot", retention_days=30)

        remaining = {p.name for p in self.log_dir.iterdir()}
        self.assertNotIn(f"sip_bot_{old_date.isoformat()}.log", remaining,
                          "file older than retention_days should be pruned")
        self.assertIn(f"sip_bot_{recent_date.isoformat()}.log", remaining,
                       "file within retention_days should be kept")
        self.assertIn(f"sip_bot_{today.isoformat()}.log", remaining,
                       "today's file should be kept")
        self.assertIn("not_a_dated_file.log", remaining,
                       "non-dated files should never be touched by pruning")

    def test_zero_or_none_retention_disables_pruning(self):
        old_date = date(2020, 1, 1)
        self._touch(f"sip_bot_{old_date.isoformat()}.log")

        logger_setup._prune_old_logs(self.log_dir, "sip_bot", retention_days=0)
        logger_setup._prune_old_logs(self.log_dir, "sip_bot", retention_days=None)

        self.assertTrue((self.log_dir / f"sip_bot_{old_date.isoformat()}.log").exists())


class TestConfigureRootIntegration(unittest.TestCase):
    """Exercises get_logger()/_configure_root() end to end against the
    real config.json, confirming the wired-up logger actually produces
    a dated file rather than the old sip_bot.log."""

    def setUp(self):
        self.tmpdir = tempfile.mkdtemp()
        logger_setup._CONFIGURED = False
        # Reset the "sip_bot" logger's handlers between tests so repeated
        # runs of this test class don't accumulate duplicate handlers.
        import logging
        root = logging.getLogger("sip_bot")
        for h in list(root.handlers):
            root.removeHandler(h)

    def tearDown(self):
        shutil.rmtree(self.tmpdir, ignore_errors=True)
        logger_setup._CONFIGURED = False
        import logging
        root = logging.getLogger("sip_bot")
        for h in list(root.handlers):
            root.removeHandler(h)

    def test_get_logger_writes_to_dated_file_under_configured_log_dir(self):
        import logging
        from pathlib import Path

        fixed_today = date(2026, 8, 19)

        def fake_get_config():
            return {
                "logging": {
                    "log_dir": self.tmpdir,
                    "level": "INFO",
                    "filename_prefix": "sip_bot",
                    "retention_days": 30,
                },
            }

        with patch.object(logger_setup, "get_config", side_effect=fake_get_config), \
             patch.object(logger_setup.market_time, "now_et", return_value=FakeEtDatetime(fixed_today)):
            log = logger_setup.get_logger("test_module")
            log.info("integration test message")
            for h in logging.getLogger("sip_bot").handlers:
                h.flush()

        # log_dir is joined onto logger_setup.py's own directory per
        # _configure_root()'s Path(__file__).resolve().parent / log_dir
        # -- since we passed an absolute tmpdir path, Path's / operator
        # with an absolute right-hand side returns that absolute path
        # unchanged, so the file should land directly in self.tmpdir.
        expected = Path(self.tmpdir) / "sip_bot_2026-08-19.log"
        self.assertTrue(expected.exists(), f"expected {expected}, found {list(Path(self.tmpdir).iterdir())}")
        self.assertFalse((Path(self.tmpdir) / "sip_bot.log").exists())


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