import sys
import unittest
from pathlib import Path

import pandas as pd

SCRIPTS_DIR = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(SCRIPTS_DIR))

import audit_research_data as mod  # noqa: E402


class PathTest(unittest.TestCase):
    def test_perpetual_pair_maps_to_expected_file(self):
        self.assertEqual(
            mod.pair_file(Path("/data"), "BTC/USDT:USDT", "5m-futures").name,
            "BTC_USDT_USDT-5m-futures.feather",
        )


class FixedGridTest(unittest.TestCase):
    def test_internal_gap_is_counted_but_boundaries_are_not(self):
        frame = pd.DataFrame(
            {
                "date": pd.to_datetime(
                    [
                        "2026-01-01T00:00:00Z",
                        "2026-01-01T00:05:00Z",
                        "2026-01-01T00:15:00Z",
                    ]
                ),
                "close": [1.0, 2.0, 3.0],
            }
        )
        record, gaps = mod.analyze_frame(
            frame,
            pair="BTC/USDT:USDT",
            spec=mod.DATASETS[0],
            funding_max_gap_seconds=8 * 3600,
            listing_time=None,
        )
        self.assertEqual(record["internal_gap_count"], 1)
        self.assertEqual(record["missing_grid_points"], 1)
        self.assertEqual(len(gaps), 1)
        self.assertEqual(gaps[0]["classification"], "internal_grid_gap")

    def test_duplicates_and_nan_are_reported(self):
        frame = pd.DataFrame(
            {
                "date": pd.to_datetime(
                    ["2026-01-01T00:00:00Z", "2026-01-01T00:00:00Z"]
                ),
                "close": [1.0, float("nan")],
            }
        )
        record, _ = mod.analyze_frame(
            frame,
            pair="BTC/USDT:USDT",
            spec=mod.DATASETS[1],
            funding_max_gap_seconds=8 * 3600,
            listing_time=None,
        )
        self.assertEqual(record["duplicate_timestamps"], 1)
        self.assertEqual(record["nan_by_column"]["close"], 1)

    def test_xsmom_endpoint_requires_twenty_nine_contiguous_daily_points(self):
        frame = pd.DataFrame(
            {
                "date": pd.date_range("2026-01-01", periods=29, freq="1D", tz="UTC"),
                "close": list(range(29)),
            }
        )
        record, _ = mod.analyze_frame(
            frame,
            pair="BTC/USDT:USDT",
            spec=mod.DATASETS[1],
            funding_max_gap_seconds=8 * 3600,
            listing_time=None,
        )
        self.assertTrue(record["xsmom_28d_latest_endpoint_usable"])
        self.assertEqual(
            record["xsmom_28d_first_eligible_at"], "2026-01-30T00:00:00+00:00"
        )

    def test_xsmom_first_endpoint_skips_gap_before_first_complete_window(self):
        dates = pd.date_range("2026-01-01", periods=30, freq="1D", tz="UTC").delete(10)
        frame = pd.DataFrame({"date": dates, "close": range(len(dates))})
        record, _ = mod.analyze_frame(
            frame,
            pair="BTC/USDT:USDT",
            spec=mod.DATASETS[1],
            funding_max_gap_seconds=8 * 3600,
            listing_time=None,
        )
        self.assertIsNone(record["xsmom_28d_first_feature_asof"])
        self.assertIsNone(record["xsmom_28d_first_eligible_at"])


class FundingGridTest(unittest.TestCase):
    def test_variable_funding_intervals_up_to_eight_hours_are_not_gaps(self):
        frame = pd.DataFrame(
            {
                "date": pd.to_datetime(
                    [
                        "2026-01-01T00:00:00Z",
                        "2026-01-01T01:00:00Z",
                        "2026-01-01T05:00:00Z",
                        "2026-01-01T13:00:00Z",
                    ]
                ),
                "close": [0.0, 0.0, 0.0, 0.0],
            }
        )
        record, gaps = mod.analyze_frame(
            frame,
            pair="POWER/USDT:USDT",
            spec=mod.DATASETS[2],
            funding_max_gap_seconds=8 * 3600,
            listing_time=None,
        )
        self.assertEqual(record["internal_gap_count"], 0)
        self.assertEqual(gaps, [])

    def test_more_than_eight_hours_is_only_a_gap_candidate(self):
        frame = pd.DataFrame(
            {
                "date": pd.to_datetime(
                    ["2026-01-01T00:00:00Z", "2026-01-01T16:00:00Z"]
                ),
                "close": [0.0, 0.0],
            }
        )
        _, gaps = mod.analyze_frame(
            frame,
            pair="BTC/USDT:USDT",
            spec=mod.DATASETS[2],
            funding_max_gap_seconds=8 * 3600,
            listing_time=None,
        )
        self.assertEqual(gaps[0]["classification"], "funding_event_gap_candidate")


class ListingMetadataTest(unittest.TestCase):
    def test_listing_date_is_never_inferred(self):
        frame = pd.DataFrame(
            {
                "date": pd.to_datetime(["2026-03-19T00:00:00Z"]),
                "close": [1.0],
            }
        )
        record, _ = mod.analyze_frame(
            frame,
            pair="EDGE/USDT:USDT",
            spec=mod.DATASETS[1],
            funding_max_gap_seconds=8 * 3600,
            listing_time=None,
        )
        self.assertIsNone(record["listing_time"])
        self.assertIsNone(record["pre_listing"])

    def test_power_intraday_listing_excludes_prelisting_and_partial_day(self):
        frame = pd.DataFrame(
            {
                "date": pd.date_range(
                    "2025-12-01", "2026-01-04", freq="1D", tz="UTC"
                ),
                "close": range(35),
            }
        )
        record, _ = mod.analyze_frame(
            frame,
            pair="POWER/USDT:USDT",
            spec=mod.DATASETS[1],
            funding_max_gap_seconds=8 * 3600,
            listing_time=pd.Timestamp("2025-12-06T09:00:00Z"),
        )
        self.assertEqual(
            record["xsmom_28d_first_feature_asof"],
            "2026-01-04T00:00:00+00:00",
        )
        self.assertEqual(
            record["xsmom_28d_first_eligible_at"],
            "2026-01-05T00:00:00+00:00",
        )

    def test_edge_intraday_listing_excludes_prelisting_and_partial_day(self):
        frame = pd.DataFrame(
            {
                "date": pd.date_range(
                    "2026-03-01", "2026-04-17", freq="1D", tz="UTC"
                ),
                "close": range(48),
            }
        )
        record, _ = mod.analyze_frame(
            frame,
            pair="EDGE/USDT:USDT",
            spec=mod.DATASETS[1],
            funding_max_gap_seconds=8 * 3600,
            listing_time=pd.Timestamp("2026-03-19T14:00:00Z"),
        )
        self.assertEqual(
            record["xsmom_28d_first_feature_asof"],
            "2026-04-17T00:00:00+00:00",
        )
        self.assertEqual(
            record["xsmom_28d_first_eligible_at"],
            "2026-04-18T00:00:00+00:00",
        )

    def test_midnight_listing_keeps_listing_day(self):
        frame = pd.DataFrame(
            {
                "date": pd.date_range(
                    "2026-01-01", periods=29, freq="1D", tz="UTC"
                ),
                "close": range(29),
            }
        )
        record, _ = mod.analyze_frame(
            frame,
            pair="BTC/USDT:USDT",
            spec=mod.DATASETS[1],
            funding_max_gap_seconds=8 * 3600,
            listing_time=pd.Timestamp("2026-01-01T00:00:00Z"),
        )
        self.assertEqual(
            record["xsmom_28d_first_feature_asof"],
            "2026-01-29T00:00:00+00:00",
        )
        self.assertEqual(
            record["xsmom_28d_first_eligible_at"],
            "2026-01-30T00:00:00+00:00",
        )


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