from __future__ import annotations

import pyarrow as pa
import pyarrow.parquet as pq

from libs.intraday.cache import DailyBarCache, IntradayCache


def test_legacy_cache_file_is_treated_as_miss_and_removed(tmp_path) -> None:
    cache = IntradayCache(str(tmp_path))
    cache_path = tmp_path / "AAPL" / "2026-01-05.parquet"
    cache_path.parent.mkdir(parents=True, exist_ok=True)

    legacy = pa.table({
        "timestamp": ["2026-01-05T13:30:00Z"],
        "open": [100.0],
        "high": [101.0],
        "low": [99.5],
        "close": [100.5],
        "volume": [1000.0],
        "vwap": [100.4],
    })
    pq.write_table(legacy, str(cache_path))

    assert cache.has("AAPL", "2026-01-05") is False
    assert cache.get("AAPL", "2026-01-05") is None
    assert cache_path.exists() is False


def test_cache_put_writes_metadata_valid_file(tmp_path) -> None:
    cache = IntradayCache(str(tmp_path))
    bars = []
    for i in range(10):
        bars.append({
            "timestamp": f"2026-01-05T13:{30 + i:02d}:00Z",
            "open": 100.0,
            "high": 101.0,
            "low": 99.5,
            "close": 100.5,
            "volume": 1000.0,
            "vwap": 100.4,
        })

    cache.put("AAPL", "2026-01-05", bars)

    assert cache.has("AAPL", "2026-01-05") is True
    assert cache.get("AAPL", "2026-01-05") == bars


def test_intraday_cache_rejects_too_few_rows(tmp_path) -> None:
    cache = IntradayCache(str(tmp_path))
    cache_path = tmp_path / "AAPL" / "2026-01-05.parquet"
    cache_path.parent.mkdir(parents=True, exist_ok=True)

    tiny = pa.table({
        "timestamp": [f"2026-01-05T13:{30+i:02d}:00Z" for i in range(5)],
        "open": [100.0] * 5,
        "high": [101.0] * 5,
        "low": [99.5] * 5,
        "close": [100.5] * 5,
        "volume": [1000.0] * 5,
        "vwap": [100.4] * 5,
    }, schema=pa.schema([
        pa.field("timestamp", pa.string()),
        pa.field("open", pa.float64()),
        pa.field("high", pa.float64()),
        pa.field("low", pa.float64()),
        pa.field("close", pa.float64()),
        pa.field("volume", pa.float64()),
        pa.field("vwap", pa.float64()),
    ]).with_metadata({
        b"intraday_cache_version": b"3",
        b"intraday_cache_source": b"api_v1_alpaca_intraday",
        b"intraday_cache_interval": b"5min",
    }))
    pq.write_table(tiny, str(cache_path))

    assert cache.has("AAPL", "2026-01-05") is False
    assert cache_path.exists() is False


def test_intraday_cache_accepts_existing_positive_file_without_kind_metadata(tmp_path) -> None:
    cache = IntradayCache(str(tmp_path))
    cache_path = tmp_path / "AAPL" / "2026-01-05.parquet"
    cache_path.parent.mkdir(parents=True, exist_ok=True)

    rows = 12
    table = pa.table({
        "timestamp": [f"2026-01-05T13:{30+i:02d}:00Z" for i in range(rows)],
        "open": [100.0] * rows,
        "high": [101.0] * rows,
        "low": [99.5] * rows,
        "close": [100.5] * rows,
        "volume": [1000.0] * rows,
        "vwap": [100.4] * rows,
    }, schema=pa.schema([
        pa.field("timestamp", pa.string()),
        pa.field("open", pa.float64()),
        pa.field("high", pa.float64()),
        pa.field("low", pa.float64()),
        pa.field("close", pa.float64()),
        pa.field("volume", pa.float64()),
        pa.field("vwap", pa.float64()),
    ]).with_metadata({
        b"intraday_cache_version": b"3",
        b"intraday_cache_source": b"api_v1_alpaca_intraday",
        b"intraday_cache_interval": b"5min",
    }))
    pq.write_table(table, str(cache_path))

    assert cache.has("AAPL", "2026-01-05") is True
    assert len(cache.get("AAPL", "2026-01-05") or []) == rows


def test_intraday_cache_negative_entry_suppresses_rereads(tmp_path) -> None:
    cache = IntradayCache(str(tmp_path))

    cache.put_negative("AAPL", "2026-01-05", reason="sparse")

    assert cache.has("AAPL", "2026-01-05") is True
    assert cache.get("AAPL", "2026-01-05") == []


def test_daily_cache_put_and_get_uses_requested_coverage_range(tmp_path) -> None:
    cache = DailyBarCache(str(tmp_path))
    bars = [
        {
            "date": "2026-01-02",
            "open": 100.0,
            "high": 101.0,
            "low": 99.0,
            "close": 100.5,
            "volume": 1000.0,
        },
        {
            "date": "2026-01-05",
            "open": 101.0,
            "high": 102.0,
            "low": 100.0,
            "close": 101.5,
            "volume": 1200.0,
        },
    ]

    cache.put("AAPL", "2026-01-01", "2026-01-10", bars)

    assert cache.get("AAPL", "2026-01-01", "2026-01-10") == bars
    assert cache.get("AAPL", "2026-01-02", "2026-01-05") == bars
    assert cache.get("AAPL", "2025-12-31", "2026-01-10") is None


def test_daily_cache_rejects_egregiously_partial_warmup_file(tmp_path) -> None:
    cache = DailyBarCache(str(tmp_path))
    cache_path = tmp_path / "BLD.parquet"
    rows = []
    for i in range(11):
        rows.append(
            {
                "date": f"2026-04-{6 + i:02d}",
                "open": 100.0 + i,
                "high": 101.0 + i,
                "low": 99.0 + i,
                "close": 100.5 + i,
                "volume": 1_000.0 + i,
            }
        )

    schema = pa.schema([
        pa.field("date", pa.string()),
        pa.field("open", pa.float64()),
        pa.field("high", pa.float64()),
        pa.field("low", pa.float64()),
        pa.field("close", pa.float64()),
        pa.field("volume", pa.float64()),
    ]).with_metadata({
        b"daily_cache_version": b"1",
        b"daily_cache_source": b"api_v1_price_data",
        b"daily_cache_interval": b"1d",
        b"daily_cache_coverage_start": b"2026-01-20",
        b"daily_cache_coverage_end": b"2026-04-20",
    })
    pq.write_table(pa.Table.from_pylist(rows, schema=schema), str(cache_path))

    assert cache.get("BLD", "2026-01-20", "2026-04-20") is None
    assert cache_path.exists() is False
