import json

import numpy as np
import pandas as pd
import pytest

from tools import train_mlp
from tools import run_mlp_pipeline_comparison as pipeline


def test_purge_removes_exact_labels_crossing_validation():
    times = pd.Series(pd.date_range('2020-01-01', periods=40, freq='4h'))
    valid = np.ones(40, dtype=bool)
    mask = train_mlp.purged_training_mask(valid, times, times.iloc[25], 10)
    assert np.flatnonzero(mask).tolist() == list(range(15))
    assert (times.shift(-10)[mask] < times.iloc[25]).all()


def test_training_loader_epoch_and_full_final_day(tmp_path, monkeypatch):
    times = pd.date_range('2020-01-01', periods=13, freq='4h')
    path = tmp_path / 'input.csv'
    pd.DataFrame({'time': [int(t.timestamp()) for t in times], 'close': 1}).to_csv(path, index=False)
    monkeypatch.setattr(train_mlp.config, 'TRAIN_START', '2020-01-01')
    monkeypatch.setattr(train_mlp.config, 'TRAIN_END', '2020-01-02')
    got = train_mlp.load_data(path)
    assert len(got) == 12
    assert got.time.iloc[-1] == pd.Timestamp('2020-01-02 20:00')


def test_splits_exclude_outer_from_all_selection():
    for year in pipeline.YEARS:
        for arm in pipeline.ARMS:
            p = pipeline.split_plan(year, arm)
            assert p['start'] < p['validation'] < p['calibration'] < p['origin'] < p['end']
            assert all(p['start'] <= a < b <= p['calibration'] for a, b in p['folds'])
            if arm == 'five_year':
                assert p['start'] == p['origin'] - pd.DateOffset(years=5)
                assert p['start'] >= pd.Timestamp('2017-12-01')
    assert len(pipeline.YEARS) * len(pipeline.ARMS) * len(pipeline.SEEDS) == 24


def test_equal_threshold_budgets_and_draws():
    a = list(pipeline.threshold_candidates([1, 2, 3, 4]))
    b = list(pipeline.threshold_candidates([5, 6, 7, 8]))
    assert len(a) == len(b) == 128
    np.testing.assert_array_equal(a[1:], b[1:])


def test_schema_keeps_duplicate_slots_and_rejects_missing(tmp_path):
    assert len(pipeline.FEATURE_COLS) == 55
    assert len(set(pipeline.FEATURE_COLS)) == 53
    path = tmp_path / 'bad.csv'
    pd.DataFrame({'time': [1, 2], 'close': [1, 1], 'high': [1, 1]}).to_csv(path, index=False)
    with pytest.raises(ValueError, match='Missing'):
        pipeline.load_inputs(path)


def test_selection_freeze_contains_no_outer_metrics(tmp_path):
    rows = [dict(seed=101, metrics=dict(calmar=1, pnl_pct=5, dd_pct=-5), artifact='x', sha256='abc'),
            dict(seed=202, metrics=dict(calmar=2, pnl_pct=4, dd_pct=-2), artifact='y', sha256='def')]
    path = tmp_path / 'selection.json'
    frozen = pipeline.freeze_selection(path, rows, pipeline.split_plan(2023, 'five_year'))
    assert frozen['selected']['seed'] == 202
    assert json.loads(path.read_text()) == frozen
    assert 'outer' not in frozen


def test_trend_baseline_cannot_use_same_day_close():
    frame = pd.DataFrame({'time': pd.date_range('2020-01-01', periods=210, freq='D'), 'close': np.arange(210) + 10., 'high': np.arange(210)+11.})
    altered = frame.copy()
    altered.loc[205, 'close'] = .01
    first = pipeline.baseline_signals(frame, frame.time.iloc[200], 'trend')
    second = pipeline.baseline_signals(altered, frame.time.iloc[200], 'trend')
    assert first.position.iloc[205] == second.position.iloc[205]
    assert first.position.iloc[206] != second.position.iloc[206]


def test_fitness_preserves_preboundary_history_and_full_end_day(monkeypatch):
    times = pd.Series(pd.date_range('2020-01-01', periods=24, freq='4h'))
    frame = pd.DataFrame({'time': times, 'close': 100., 'high': 101.})
    layers = [(np.zeros((1, 10)), np.zeros(1))]
    theta = np.r_[train_mlp.flatten_layers(layers), [0., 0., 0., 0.]]
    context = train_mlp.make_fitness_context(frame, np.zeros((24, 10)), [10, 1], theta[:11],
        [(None, '2020-01-02', '2020-01-03')], 1, 1, 0.)
    monkeypatch.setattr(train_mlp, '_score_to_signals', lambda d, *args: d)
    seen = {}
    def metrics(sl, **kwargs):
        seen['first'] = sl.time.iloc[0]
        seen['last'] = sl.time.iloc[-1]
        seen.update(kwargs)
        return {'Calmar Ratio': 1., 'Total Trades': 1}
    monkeypatch.setattr(train_mlp, 'calculate_metrics', metrics)
    train_mlp.evaluate_theta(theta, context)
    assert seen['first'] == times.iloc[0]
    assert seen['last'] == pd.Timestamp('2020-01-03 20:00')
    assert seen['score_start'] == '2020-01-02'


def test_nonempty_output_refused_before_mutation(tmp_path):
    from types import SimpleNamespace
    marker = tmp_path / 'partial.json'
    marker.write_text('original')
    with pytest.raises(ValueError, match='nonempty'):
        pipeline.run(SimpleNamespace(out=str(tmp_path)))
    assert marker.read_text() == 'original'
