"""Tests for TGTC V2 1-minute bar cache (libs/tgtc/cache_1m.py)."""
import datetime as dt
import tempfile
from pathlib import Path
from unittest.mock import patch, MagicMock

import pytest

from libs.intraday.cache import IntradayCache


# ── IntradayCache interval_minutes parameter ──────────────────────────────────

def test_intraday_cache_default_interval():
    """Default IntradayCache still writes/reads 5-min metadata."""
    with tempfile.TemporaryDirectory() as tmp:
        cache = IntradayCache(cache_dir=tmp, interval_minutes=5)
        meta = cache._cache_metadata
        assert meta[b"intraday_cache_interval"] == b"5min"


def test_intraday_cache_1min_interval():
    """IntradayCache with interval_minutes=1 writes 1min metadata."""
    with tempfile.TemporaryDirectory() as tmp:
        cache = IntradayCache(cache_dir=tmp, interval_minutes=1)
        meta = cache._cache_metadata
        assert meta[b"intraday_cache_interval"] == b"1min"


def test_intraday_cache_1min_round_trip():
    """Writing and reading 1-min bars through IntradayCache round-trips correctly."""
    with tempfile.TemporaryDirectory() as tmp:
        cache = IntradayCache(cache_dir=tmp, interval_minutes=1)
        bars = [
            {"timestamp": f"2025-09-12T{9+h:02d}:{m:02d}:00+00:00",
             "open": 100.0, "high": 101.0, "low": 99.5, "close": 100.5,
             "volume": 1000.0, "vwap": 100.2}
            for h in range(0, 7) for m in range(0, 60, 1)
        ][:60]  # 60 bars = 1 hour of 1-min bars
        cache.put("TEST", "2025-09-12", bars)

        readback = cache.get("TEST", "2025-09-12")
        assert readback is not None
        assert len(readback) == 60
        assert readback[0]["timestamp"] == bars[0]["timestamp"]


def test_intraday_1min_cache_does_not_validate_5min_files():
    """A 5-min cache file should NOT be valid in a 1-min cache (metadata mismatch)."""
    with tempfile.TemporaryDirectory() as tmp:
        # Write a file via 5-min cache
        cache_5m = IntradayCache(cache_dir=tmp, interval_minutes=5)
        bars = [
            {"timestamp": f"2025-09-12T{9+h:02d}:{m:02d}:00+00:00",
             "open": 100.0, "high": 101.0, "low": 99.5, "close": 100.5,
             "volume": 1000.0, "vwap": 100.2}
            for h in range(0, 2) for m in range(0, 60, 5)
        ]  # 24 bars
        cache_5m.put("TEST", "2025-09-12", bars)

        # 1-min cache should NOT see it as valid (interval mismatch)
        cache_1m = IntradayCache(cache_dir=tmp, interval_minutes=1)
        result = cache_1m.get("TEST", "2025-09-12")
        assert result is None  # invalid metadata → miss


# ── ensure_1m_bars (mocked Oracle) ────────────────────────────────────────────

def _make_bars(n: int) -> list[dict]:
    return [
        {"timestamp": f"2025-09-12T14:{m:02d}:00+00:00",
         "open": 100.0, "high": 101.0, "low": 99.5, "close": 100.5,
         "volume": 500.0, "vwap": 100.2}
        for m in range(n)
    ]


def test_ensure_1m_bars_cache_miss_fetches_from_api():
    """On cache miss, ensure_1m_bars fetches from Oracle and caches the result."""
    with tempfile.TemporaryDirectory() as tmp:
        with patch("libs.tgtc.cache_1m._CACHE_DIR", tmp), \
             patch("libs.tgtc.cache_1m._cache", None), \
             patch("libs.oracle_client.alpaca.get_multi_intraday_bars",
                   return_value={"AAPL": _make_bars(390)}) as mock_fetch:
            from libs.tgtc import cache_1m
            cache_1m._cache = None  # reset singleton

            result = cache_1m.ensure_1m_bars(["AAPL"], dt.date(2025, 9, 12))
            assert "AAPL" in result
            assert len(result["AAPL"]) == 390
            mock_fetch.assert_called_once()

            # Second call should be served from cache (mock not called again)
            result2 = cache_1m.ensure_1m_bars(["AAPL"], dt.date(2025, 9, 12))
            assert "AAPL" in result2
            mock_fetch.assert_called_once()  # still only once


def test_ensure_1m_bars_negative_cache_on_empty():
    """Symbols with no API bars get a negative cache entry."""
    with tempfile.TemporaryDirectory() as tmp:
        with patch("libs.tgtc.cache_1m._CACHE_DIR", tmp), \
             patch("libs.tgtc.cache_1m._cache", None), \
             patch("libs.oracle_client.alpaca.get_multi_intraday_bars",
                   return_value={}):
            from libs.tgtc import cache_1m
            cache_1m._cache = None

            result = cache_1m.ensure_1m_bars(["EMPTY"], dt.date(2025, 9, 12))
            assert "EMPTY" not in result

            # Second call should not fetch from API again
            with patch("libs.oracle_client.alpaca.get_multi_intraday_bars") as mock2:
                cache_1m.ensure_1m_bars(["EMPTY"], dt.date(2025, 9, 12))
                mock2.assert_not_called()


# ── DST-aware ET→UTC conversion ────────────────────────────────────────────────

def test_et_to_utc_naive_edt():
    """In EDT (summer), 10:00 ET = 14:00 UTC."""
    from libs.tgtc.gainers_reconstruct import _et_to_utc_naive
    import datetime as dt
    date = dt.date(2025, 9, 12)  # September = EDT
    utc = _et_to_utc_naive(date, 10, 0)
    assert utc.hour == 14
    assert utc.minute == 0


def test_et_to_utc_naive_est():
    """In EST (winter), 10:00 ET = 15:00 UTC."""
    from libs.tgtc.gainers_reconstruct import _et_to_utc_naive
    import datetime as dt
    date = dt.date(2025, 1, 15)  # January = EST
    utc = _et_to_utc_naive(date, 10, 0)
    assert utc.hour == 15
    assert utc.minute == 0
