"""
Tests for Indicator Base Class - MTF Support.

Tests the align() method with pre-computed expected values.
"""

import pytest
import numpy as np
import pandas as pd
from datetime import datetime, timezone, timedelta

from sixtysix.core.indicator import Indicator


def make_hourly_timestamps(n: int, start_hour: int = 0) -> np.ndarray:
    """Create hourly timestamps starting from midnight Jan 1, 2024."""
    start = datetime(2024, 1, 1, start_hour, tzinfo=timezone.utc)
    return np.array([
        int((start + timedelta(hours=i)).timestamp() * 1000)
        for i in range(n)
    ])


def make_daily_timestamps(n: int) -> np.ndarray:
    """Create daily timestamps starting from midnight Jan 1, 2024."""
    start = datetime(2024, 1, 1, tzinfo=timezone.utc)
    return np.array([
        int((start + timedelta(days=i)).timestamp() * 1000)
        for i in range(n)
    ])


def make_4h_timestamps(n: int) -> np.ndarray:
    """Create 4-hour timestamps starting from midnight Jan 1, 2024."""
    start = datetime(2024, 1, 1, tzinfo=timezone.utc)
    return np.array([
        int((start + timedelta(hours=i * 4)).timestamp() * 1000)
        for i in range(n)
    ])


class TestIndicatorAlign:
    """Tests for Indicator.align() method."""

    def setup_method(self):
        """Create a test indicator instance."""
        self.indicator = Indicator()

    def test_align_hourly_to_daily(self):
        """Test aligning daily values to hourly base - 48 hours, 2 days."""
        # Setup: 48 hourly bars (2 days), 2 daily bars
        hourly_ts = make_hourly_timestamps(48)
        daily_ts = make_daily_timestamps(2)

        base_df = pd.DataFrame({'timestamp': hourly_ts})
        mtf_df = pd.DataFrame({'timestamp': daily_ts})

        # Daily values: day 0 = 100.0, day 1 = 200.0
        daily_values = pd.Series([100.0, 200.0])

        # Setup indicator MTF state
        self.indicator._mtf_data = {'1D': mtf_df}
        self.indicator._current_df = base_df
        self.indicator._precompute_mtf_indices(base_df)

        # Align
        result = self.indicator.align(daily_values, '1D')

        # Expected: hours 0-23 get 100.0, hours 24-47 get 200.0
        expected = np.array([100.0] * 24 + [200.0] * 24)
        np.testing.assert_array_equal(result.values, expected)

    def test_align_hourly_to_4h(self):
        """Test aligning 4h values to hourly base - 24 hours, 6 4h bars."""
        # Setup: 24 hourly bars, 6 4h bars
        hourly_ts = make_hourly_timestamps(24)
        four_h_ts = make_4h_timestamps(6)

        base_df = pd.DataFrame({'timestamp': hourly_ts})
        mtf_df = pd.DataFrame({'timestamp': four_h_ts})

        # 4h values: 10, 20, 30, 40, 50, 60
        htf_values = pd.Series([10.0, 20.0, 30.0, 40.0, 50.0, 60.0])

        self.indicator._mtf_data = {'4h': mtf_df}
        self.indicator._current_df = base_df
        self.indicator._precompute_mtf_indices(base_df)

        result = self.indicator.align(htf_values, '4h')

        # Expected: each 4h value repeated 4 times
        expected = np.array([
            10.0, 10.0, 10.0, 10.0,
            20.0, 20.0, 20.0, 20.0,
            30.0, 30.0, 30.0, 30.0,
            40.0, 40.0, 40.0, 40.0,
            50.0, 50.0, 50.0, 50.0,
            60.0, 60.0, 60.0, 60.0,
        ])
        np.testing.assert_array_equal(result.values, expected)

    def test_align_preserves_values_at_boundaries(self):
        """Test that boundary bars get correct values."""
        # 25 hourly bars: hours 0-23 (day 0), hour 24 (day 1)
        hourly_ts = make_hourly_timestamps(25)
        daily_ts = make_daily_timestamps(2)

        base_df = pd.DataFrame({'timestamp': hourly_ts})
        mtf_df = pd.DataFrame({'timestamp': daily_ts})

        daily_values = pd.Series([111.11, 222.22])

        self.indicator._mtf_data = {'1D': mtf_df}
        self.indicator._current_df = base_df
        self.indicator._precompute_mtf_indices(base_df)

        result = self.indicator.align(daily_values, '1D')

        # Hour 23 (last of day 0) should be 111.11
        assert result.iloc[23] == 111.11
        # Hour 24 (first of day 1) should be 222.22
        assert result.iloc[24] == 222.22

    def test_align_single_htf_bar(self):
        """Test when all base bars map to single HTF bar."""
        hourly_ts = make_hourly_timestamps(12)
        daily_ts = make_daily_timestamps(1)

        base_df = pd.DataFrame({'timestamp': hourly_ts})
        mtf_df = pd.DataFrame({'timestamp': daily_ts})

        daily_values = pd.Series([999.99])

        self.indicator._mtf_data = {'1D': mtf_df}
        self.indicator._current_df = base_df
        self.indicator._precompute_mtf_indices(base_df)

        result = self.indicator.align(daily_values, '1D')

        expected = np.array([999.99] * 12)
        np.testing.assert_array_equal(result.values, expected)

    def test_align_empty_series_returns_empty(self):
        """Test that empty input returns empty series."""
        result = self.indicator.align(pd.Series(dtype=float), '1D')
        assert len(result) == 0

    def test_align_none_returns_empty(self):
        """Test that None input returns empty series."""
        result = self.indicator.align(None, '1D')
        assert len(result) == 0

    def test_align_fallback_when_no_indices(self):
        """Test fallback behavior when MTF indices not precomputed."""
        base_df = pd.DataFrame({'timestamp': make_hourly_timestamps(10)})
        self.indicator._current_df = base_df
        self.indicator._mtf_bar_indices = None

        htf_values = pd.Series([100.0, 200.0, 300.0])

        result = self.indicator.align(htf_values, '1D')

        # Should fill with last value
        expected = np.array([300.0] * 10)
        np.testing.assert_array_equal(result.values, expected)


class TestAlignWithRealIndicatorValues:
    """
    Test align() with realistic indicator-like values.

    Simulates SMA values that change daily.
    """

    def setup_method(self):
        self.indicator = Indicator()

    def test_align_sma_like_values(self):
        """Test with SMA-like gradually changing values."""
        # 72 hourly bars (3 days)
        hourly_ts = make_hourly_timestamps(72)
        daily_ts = make_daily_timestamps(3)

        base_df = pd.DataFrame({'timestamp': hourly_ts})
        mtf_df = pd.DataFrame({'timestamp': daily_ts})

        # Simulate daily SMA values: 45000, 45500, 46000
        daily_sma = pd.Series([45000.0, 45500.0, 46000.0])

        self.indicator._mtf_data = {'1D': mtf_df}
        self.indicator._current_df = base_df
        self.indicator._precompute_mtf_indices(base_df)

        result = self.indicator.align(daily_sma, '1D')

        # Verify length
        assert len(result) == 72

        # Verify day boundaries
        # Day 0: hours 0-23
        assert all(result.iloc[0:24] == 45000.0)
        # Day 1: hours 24-47
        assert all(result.iloc[24:48] == 45500.0)
        # Day 2: hours 48-71
        assert all(result.iloc[48:72] == 46000.0)

        # Spot check specific values
        assert result.iloc[0] == 45000.0
        assert result.iloc[23] == 45000.0
        assert result.iloc[24] == 45500.0
        assert result.iloc[47] == 45500.0
        assert result.iloc[48] == 46000.0
        assert result.iloc[71] == 46000.0


if __name__ == '__main__':
    pytest.main([__file__, '-v'])
