import json
import os
import tempfile
import unittest

from scan_trend_break import (
    TV_COLUMNS,
    _parse_tradingview_rows,
    selection_quality,
)
from trend_break_audit import write_audit
from trend_break_history import archive_daily_snapshot
from trend_break_outcomes import build_outcome_ledger


def tv_values(**overrides):
    values = {
        "close": 100,
        "open": 98,
        "high": 101,
        "low": 97,
        "close[1]": 97,
        "change": 3,
        "volume": 2_000_000,
        "average_volume_10d_calc": 1_000_000,
        "relative_volume_10d_calc": 2,
        "relative_volume_intraday|5": 2.5,
        "Value.Traded": 2_000_000_000,
        "SMA20": 96,
        "SMA50": 99,
        "High.1M": 100,
        "High.3M": 120,
        "Perf.W": 4,
        "Perf.1M": 2,
        "Perf.3M": -8,
        "RSI": 58,
        "ADX": 24,
        "ADX+DI": 28,
        "ADX-DI": 18,
        "ATR": 3,
        "VWAP": 99,
        "MACD.macd": 1.2,
        "MACD.signal": 0.8,
        "sector": "Finance",
        "industry": "Banks",
    }
    values.update(overrides)
    return [values[column] for column in TV_COLUMNS]


def snapshot(timestamp, phase="LIVE", market_open=True):
    return {
        "timestamp_iso": timestamp,
        "snapshot_phase": phase,
        "market_open": market_open,
        "source": "TradingView",
        "scan_mode": "market-wide-auto-capable",
        "feature_schema_version": "2.0",
        "universe_count": 800,
        "candidate_count": 1,
        "display_count": 1,
        "rejected_count": 0,
        "entered_tickers": ["TEST"] if phase == "LIVE" else [],
        "exited_tickers": [],
        "signals": [{
            "ticker": "TEST",
            "price": 100,
            "status": "CONFIRM",
            "score": 80,
            "selection_score": 78,
            "screening_status": "KANDIDAT FULL PA",
            "technical_features": {
                "rsi_1d": 58,
                "adx_1d": 24,
                "di_spread_1d": 10,
                "atr_pct_1d": 3,
                "vwap_distance_pct": 1,
                "macd_gap_pct": 0.4,
                "sma20_distance_pct": 4,
                "sma50_distance_pct": 1,
                "perf_week_pct": 4,
                "perf_month_pct": 2,
                "perf_3m_pct": -8,
                "break_margin_pct": 0,
                "dist_sma50_pct": 1,
                "close_position_pct": 75,
                "body_to_range_pct": 50,
                "upper_wick_pct": 20,
                "volume_ratio_daily": 2,
                "volume_ratio_intraday": 2.5,
                "value_traded": 2_000_000_000,
                "prior_downtrend": True,
                "downtrend_proxy": "SMA20_BELOW_SMA50",
                "signal_class": "CONFIRM",
                "ihsg_change_pct": 0.5,
                "ihsg_rsi_1d": 55,
                "ihsg_adx_1d": 20,
                "ihsg_regime": "NEUTRAL",
            },
        }],
        "rejected_signals": [],
    }


class TrendBreakLearningTest(unittest.TestCase):
    def test_parser_requires_prior_downtrend_proxy(self):
        parsed = _parse_tradingview_rows({
            "data": [{"s": "IDX:TEST", "d": tv_values()}],
        })
        self.assertIn("TEST", parsed)
        self.assertTrue(parsed["TEST"]["prior_downtrend"])
        self.assertEqual(parsed["TEST"]["status"], "CONFIRM")

        no_downtrend = _parse_tradingview_rows({
            "data": [{
                "s": "IDX:NODN",
                "d": tv_values(
                    close=103,
                    open=100,
                    high=104,
                    SMA20=101,
                    SMA50=99,
                    **{"High.1M": 103, "Perf.3M": 8},
                ),
            }],
        })["NODN"]
        quality = selection_quality(no_downtrend)
        self.assertEqual(quality["screening_status"], "TIDAK LAYAK DITUNGGU")
        self.assertIn("downtrend", " ".join(quality["screening_reasons"]).lower())

    def test_quality_gate_uses_rupiah_liquidity_not_share_count(self):
        parsed = _parse_tradingview_rows({
            "data": [{
                "s": "IDX:ILLIQ",
                "d": tv_values(**{
                    "close": 20,
                    "open": 19,
                    "high": 20,
                    "low": 19,
                    "close[1]": 19,
                    "volume": 600_000,
                    "average_volume_10d_calc": 600_000,
                    "relative_volume_intraday|5": 2,
                    "SMA20": 18,
                    "SMA50": 19,
                    "High.1M": 20,
                }),
            }],
        })["ILLIQ"]
        self.assertLess(parsed["avg_value"], 1_000_000_000)
        self.assertEqual(
            selection_quality(parsed)["screening_status"],
            "TIDAK LAYAK DITUNGGU",
        )

    def test_premarket_never_becomes_forward_learning_record(self):
        with tempfile.TemporaryDirectory() as tmp:
            history = os.path.join(tmp, "history")
            outcomes = os.path.join(tmp, "outcomes.json")
            dataset = os.path.join(tmp, "dataset.json")
            audit = os.path.join(tmp, "audit.json")
            archive_daily_snapshot(
                snapshot(
                    "2026-07-30T08:00:00+07:00",
                    phase="PREMARKET",
                    market_open=False,
                ),
                history_dir=history,
            )
            with open(outcomes, "w", encoding="utf-8") as handle:
                json.dump({"records": []}, handle)
            report, payload = write_audit(
                history_dir=history,
                outcomes_path=outcomes,
                dataset_path=dataset,
                audit_path=audit,
                backtest_path=os.path.join(tmp, "missing.json"),
            )
            self.assertEqual(report["status"], "PASS")
            self.assertFalse(payload["records"][0]["eligible_for_learning"])
            self.assertEqual(report["dataset"]["forward_live_records"], 0)

    def test_forward_d1_outcome_drives_audit_not_backtest(self):
        with tempfile.TemporaryDirectory() as tmp:
            history = os.path.join(tmp, "history")
            outcomes = os.path.join(tmp, "outcomes.json")
            dataset = os.path.join(tmp, "dataset.json")
            audit = os.path.join(tmp, "audit.json")
            archive_daily_snapshot(
                snapshot("2026-07-30T09:01:00+07:00"),
                history_dir=history,
            )
            ledger = build_outcome_ledger(
                history_dir=history,
                bars_by_ticker={"TEST": [
                    {"date": "2026-07-31", "open": 101, "high": 104, "low": 99, "close": 103},
                ]},
            )
            with open(outcomes, "w", encoding="utf-8") as handle:
                json.dump(ledger, handle)
            report, payload = write_audit(
                history_dir=history,
                outcomes_path=outcomes,
                dataset_path=dataset,
                audit_path=audit,
                backtest_path=os.path.join(tmp, "missing.json"),
            )
            record = payload["records"][0]
            self.assertEqual(record["label_status"], "LABELED")
            self.assertEqual(record["outcome_pct"], 3.0)
            self.assertEqual(report["overall"]["forward_out_of_sample"]["samples"], 1)
            high_wr = next(
                recipe for recipe in report["recipes"]
                if recipe["id"] == "high_wr_lab"
            )
            self.assertEqual(
                high_wr["promotion_gate"]["requirements"]["minimum_winrate_pct"],
                85.0,
            )
            self.assertFalse(high_wr["promotion_gate"]["eligible"])


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