"""I/O compatibility tests: dyfinance vs yfinance.

Layer 2: Fixture-based (offline, CI-friendly) - tests recorded parquet fixtures.
Layer 3: Live comparison (network required) - calls both yfinance and dyfinance.
"""

import json
from pathlib import Path

import pandas as pd
import pytest

FIXTURES_DIR = Path(__file__).parent / "fixtures"


def _fixture_exists(name: str) -> bool:
    return (FIXTURES_DIR / f"{name}.parquet").exists()


def _load_fixture(name: str) -> pd.DataFrame:
    return pd.read_parquet(FIXTURES_DIR / f"{name}.parquet")


def _load_meta(name: str) -> dict:
    with open(FIXTURES_DIR / f"{name}.meta.json") as f:
        return json.load(f)


# ===========================================================================
# Layer 2: Fixture-based offline tests
# ===========================================================================

class TestFixtureStructure:
    """Verify recorded fixtures have expected OHLCV structure."""

    @pytest.mark.parametrize("name", [
        "aapl_1mo_1d_default",
        "aapl_5d_1d_no_actions",
        "aapl_5d_1d_no_auto_adjust",
        "aapl_5d_1d_rounding",
        "aapl_5d_1d_back_adjust",
        "aapl_5d_1h_intraday",
        "aapl_daterange_1d",
        "toyota_5d_1d_jpstock",
    ])
    def test_history_fixture_has_ohlcv(self, name):
        if not _fixture_exists(name):
            pytest.skip(f"Fixture {name} not recorded yet (run record_fixtures.py)")

        df = _load_fixture(name)
        _load_meta(name)

        assert not df.empty, f"Fixture {name} is empty"
        assert "Open" in df.columns
        assert "High" in df.columns
        assert "Low" in df.columns
        assert "Close" in df.columns
        assert "Volume" in df.columns

    @pytest.mark.parametrize("name", [
        "aapl_1mo_1d_default",
        "aapl_daterange_1d",
        "toyota_5d_1d_jpstock",
    ])
    def test_history_fixture_default_has_actions(self, name):
        """Default history() should include Dividends and Stock Splits."""
        if not _fixture_exists(name):
            pytest.skip(f"Fixture {name} not recorded yet")

        df = _load_fixture(name)
        assert "Dividends" in df.columns
        assert "Stock Splits" in df.columns

    def test_history_fixture_no_actions(self):
        name = "aapl_5d_1d_no_actions"
        if not _fixture_exists(name):
            pytest.skip(f"Fixture {name} not recorded yet")

        df = _load_fixture(name)
        assert "Dividends" not in df.columns
        assert "Stock Splits" not in df.columns

    def test_history_fixture_no_auto_adjust_has_adj_close(self):
        name = "aapl_5d_1d_no_auto_adjust"
        if not _fixture_exists(name):
            pytest.skip(f"Fixture {name} not recorded yet")

        df = _load_fixture(name)
        assert "Adj Close" in df.columns

    def test_history_fixture_default_no_adj_close(self):
        """Default history (auto_adjust=True) should not have Adj Close."""
        name = "aapl_1mo_1d_default"
        if not _fixture_exists(name):
            pytest.skip(f"Fixture {name} not recorded yet")

        df = _load_fixture(name)
        assert "Adj Close" not in df.columns

    def test_history_fixture_rounding(self):
        name = "aapl_5d_1d_rounding"
        if not _fixture_exists(name):
            pytest.skip(f"Fixture {name} not recorded yet")

        df = _load_fixture(name)
        for col in ("Open", "High", "Low", "Close"):
            if col in df.columns:
                for val in df[col].dropna():
                    assert round(val, 2) == val, f"{col} = {val} not rounded"


class TestFixtureDownloadStructure:
    @pytest.mark.parametrize("name", [
        "dl_aapl_5d_single",
        "dl_multi_5d_column",
        "dl_multi_5d_ticker",
        "dl_aapl_5d_flat",
    ])
    def test_download_fixture_not_empty(self, name):
        if not _fixture_exists(name):
            pytest.skip(f"Fixture {name} not recorded yet")

        df = _load_fixture(name)
        assert not df.empty

    def test_download_flat_has_simple_columns(self):
        name = "dl_aapl_5d_flat"
        if not _fixture_exists(name):
            pytest.skip(f"Fixture {name} not recorded yet")

        df = _load_fixture(name)
        assert not isinstance(df.columns, pd.MultiIndex)

    def test_download_multi_column_has_multiindex(self):
        name = "dl_multi_5d_column"
        if not _fixture_exists(name):
            pytest.skip(f"Fixture {name} not recorded yet")

        df = _load_fixture(name)
        assert isinstance(df.columns, pd.MultiIndex)


# ===========================================================================
# Layer 3: Live comparison tests (require network)
# ===========================================================================

@pytest.mark.network
class TestLiveHistoryCompat:
    """Compare dyfinance.Ticker.history() output against yfinance.Ticker.history()."""

    @pytest.mark.parametrize("auto_adjust", [True, False])
    @pytest.mark.parametrize("actions", [True, False])
    def test_history_columns_match(self, auto_adjust, actions):
        import dyfinance
        import yfinance as yf

        kwargs = dict(period="5d", interval="1d", auto_adjust=auto_adjust, actions=actions)
        yf_df = yf.Ticker("AAPL").history(**kwargs)
        df_df = dyfinance.Ticker("AAPL").history(**kwargs)

        assert list(yf_df.columns) == list(df_df.columns), (
            f"Column mismatch: yf={list(yf_df.columns)}, df={list(df_df.columns)}"
        )

    @pytest.mark.parametrize("auto_adjust", [True, False])
    @pytest.mark.parametrize("actions", [True, False])
    def test_history_dtypes_match(self, auto_adjust, actions):
        import dyfinance
        import yfinance as yf

        kwargs = dict(period="5d", interval="1d", auto_adjust=auto_adjust, actions=actions)
        yf_df = yf.Ticker("AAPL").history(**kwargs)
        df_df = dyfinance.Ticker("AAPL").history(**kwargs)

        for col in yf_df.columns:
            assert yf_df[col].dtype == df_df[col].dtype, (
                f"dtype mismatch for {col}: yf={yf_df[col].dtype}, df={df_df[col].dtype}"
            )

    @pytest.mark.parametrize("auto_adjust", [True, False])
    @pytest.mark.parametrize("actions", [True, False])
    def test_history_index_match(self, auto_adjust, actions):
        import dyfinance
        import yfinance as yf

        kwargs = dict(period="5d", interval="1d", auto_adjust=auto_adjust, actions=actions)
        yf_df = yf.Ticker("AAPL").history(**kwargs)
        df_df = dyfinance.Ticker("AAPL").history(**kwargs)

        assert yf_df.index.name == df_df.index.name
        assert yf_df.index.dtype == df_df.index.dtype

    @pytest.mark.parametrize("rounding", [True, False])
    def test_history_rounding_match(self, rounding):
        import dyfinance
        import yfinance as yf

        kwargs = dict(period="5d", interval="1d", rounding=rounding)
        yf_df = yf.Ticker("AAPL").history(**kwargs)
        df_df = dyfinance.Ticker("AAPL").history(**kwargs)

        assert list(yf_df.columns) == list(df_df.columns)

    @pytest.mark.parametrize("period", ["1d", "5d", "1mo"])
    def test_history_period_shape_match(self, period):
        import dyfinance
        import yfinance as yf

        yf_df = yf.Ticker("AAPL").history(period=period)
        df_df = dyfinance.Ticker("AAPL").history(period=period)

        assert yf_df.shape == df_df.shape, (
            f"Shape mismatch for period={period}: yf={yf_df.shape}, df={df_df.shape}"
        )

    def test_history_start_end_match(self):
        import dyfinance
        import yfinance as yf

        kwargs = dict(start="2024-06-01", end="2024-06-30", interval="1d")
        yf_df = yf.Ticker("AAPL").history(**kwargs)
        df_df = dyfinance.Ticker("AAPL").history(**kwargs)

        assert yf_df.shape == df_df.shape
        assert list(yf_df.columns) == list(df_df.columns)

    @pytest.mark.parametrize("interval", ["1m", "5m", "1h"])
    def test_intraday_passthrough_match(self, interval):
        """Intraday intervals should produce identical output (passthrough)."""
        import dyfinance
        import yfinance as yf

        kwargs = dict(period="1d", interval=interval)
        yf_df = yf.Ticker("AAPL").history(**kwargs)
        df_df = dyfinance.Ticker("AAPL").history(**kwargs)

        assert list(yf_df.columns) == list(df_df.columns)
        assert yf_df.index.dtype == df_df.index.dtype


@pytest.mark.network
class TestLiveDownloadCompat:
    """Compare dyfinance.download() output against yfinance.download()."""

    def test_single_ticker_columns_match(self):
        import dyfinance
        import yfinance as yf

        yf_df = yf.download("AAPL", period="5d")
        df_df = dyfinance.download("AAPL", period="5d")

        yf_cols = [str(c) for c in yf_df.columns]
        df_cols = [str(c) for c in df_df.columns]
        assert yf_cols == df_cols

    def test_multi_ticker_group_by_column(self):
        import dyfinance
        import yfinance as yf

        yf_df = yf.download(["AAPL", "MSFT"], period="5d", group_by="column")
        df_df = dyfinance.download(["AAPL", "MSFT"], period="5d", group_by="column")

        assert isinstance(yf_df.columns, pd.MultiIndex) == isinstance(df_df.columns, pd.MultiIndex)

    def test_multi_ticker_group_by_ticker(self):
        import dyfinance
        import yfinance as yf

        yf_df = yf.download(["AAPL", "MSFT"], period="5d", group_by="ticker")
        df_df = dyfinance.download(["AAPL", "MSFT"], period="5d", group_by="ticker")

        assert isinstance(yf_df.columns, pd.MultiIndex) == isinstance(df_df.columns, pd.MultiIndex)

    def test_flat_single_ticker(self):
        import dyfinance
        import yfinance as yf

        yf_df = yf.download("AAPL", period="5d", multi_level_index=False)
        df_df = dyfinance.download("AAPL", period="5d", multi_level_index=False)

        assert not isinstance(yf_df.columns, pd.MultiIndex)
        assert not isinstance(df_df.columns, pd.MultiIndex)
        assert list(yf_df.columns) == list(df_df.columns)

    @pytest.mark.parametrize("auto_adjust", [True, False])
    @pytest.mark.parametrize("actions", [True, False])
    def test_download_options_match(self, auto_adjust, actions):
        import dyfinance
        import yfinance as yf

        kwargs = dict(
            tickers="AAPL", period="5d", interval="1d",
            auto_adjust=auto_adjust, actions=actions,
            multi_level_index=False,
        )
        yf_df = yf.download(**kwargs)
        df_df = dyfinance.download(**kwargs)

        assert list(yf_df.columns) == list(df_df.columns), (
            f"Column mismatch: yf={list(yf_df.columns)}, df={list(df_df.columns)}"
        )
