import numpy as np
import pandas as pd
import pytest


def test_closed_rows_exclude_unconfirmed_and_future_data():
    from tools.mlp_observation import read_chart
    frame = pd.DataFrame({'time': pd.date_range('2026-09-24', periods=3, freq='6h').as_unit('ns').astype('int64')//10**9,
                          'close': [100., 101., 102.]})
    data = frame.to_csv(index=False).encode()
    got = read_chart(data, pd.Timestamp('2026-09-24 12:01', tz='UTC'))
    assert len(got) == 2
    with pytest.raises(ValueError, match='unclosed|future'):
        read_chart(data, pd.Timestamp('2026-09-24 06:01', tz='UTC'))


def test_history_hashes_reject_revisions_and_missing_rows():
    from tools.mlp_observation import row_hashes, require_history
    frame = pd.DataFrame({'time': pd.date_range('2026-09-24', periods=3, freq='6h'), 'close':[100.,101.,102.]})
    original = row_hashes(frame, ['close'])
    require_history(original, original)
    altered = frame.copy()
    altered.loc[1,'close'] = 99.
    with pytest.raises(ValueError, match='history'):
        require_history(original, row_hashes(altered, ['close']))
    with pytest.raises(ValueError, match='history'):
        require_history(original, original[1:])


def test_coverage_requires_days_and_completed_trades():
    from tools.mlp_observation import coverage_status
    assert coverage_status(0,0) == 'awaiting_data'
    assert coverage_status(200,9) == 'insufficient_coverage'
    assert coverage_status(179,10) == 'insufficient_coverage'
    assert coverage_status(180,10) == 'ready_for_review'


@pytest.fixture
def frozen_bundle(tmp_path, monkeypatch):
    from tools import mlp_observation as observer
    monkeypatch.setattr(observer, 'now_utc', lambda: pd.Timestamp('2026-08-22 01:00', tz='UTC'))
    bundle = tmp_path / 'frozen'
    data = observer.REPO / 'data/mlp/COINBASE_BTCUSD, 360.csv'
    observer.freeze(bundle, data)
    return observer, bundle, data


def test_actual_freeze_no_data_repeat_and_tamper(frozen_bundle):
    import json
    observer, bundle, data = frozen_bundle
    report = observer.observe(bundle, data)
    assert report['status'] == 'awaiting_data'
    assert all(p['metrics'] is None for p in report['policies'])
    assert observer.observe(bundle, data) == report
    assert len(list((bundle/'observations').glob('*/receipt.json'))) == 1
    manifest = json.loads((bundle/'manifest.json').read_text())
    assert manifest['observation_start'] == '2026-08-22T06:00:00+00:00'
    assert manifest['policies'][1]['params']['i_long_exit_activation_confirmation_threshold'] == -730
    (bundle/'models/hybrid9090.json').write_text('{}')
    with pytest.raises(ValueError, match='Frozen bundle changed'):
        observer.observe(bundle, data)


def test_synthetic_forward_extension_is_continuous_and_cannot_be_revised(frozen_bundle, tmp_path, monkeypatch):
    observer, bundle, data = frozen_bundle
    observer.observe(bundle, data)
    # Preserve locked raw CSV lines byte-for-byte. Replace only the previously
    # excluded tail and append explicitly synthetic bars for plumbing tests.
    raw = data.read_text().strip().splitlines()
    frame = pd.read_csv(data)
    times = pd.date_range('2026-08-21 12:00', '2026-08-24 00:00', freq='6h')
    extra = pd.concat([frame.iloc[[-2]]] * len(times), ignore_index=True)
    extra['time'] = times.as_unit('ns').astype('int64') // 10**9
    extended = tmp_path/'synthetic.csv'
    extended.write_text('\n'.join(raw[:-1])+'\n'+extra.to_csv(index=False, header=False))
    monkeypatch.setattr(observer, 'now_utc', lambda: pd.Timestamp('2026-08-24 01:00', tz='UTC'))
    report = observer.observe(bundle, extended)
    assert report['status'] == 'observing'
    assert report['calendar_days'] == pytest.approx(1.75)
    assert all(p['status'] == 'insufficient_coverage' for p in report['policies'])
    assert all(p['metrics']['carry_in'] is False for p in report['policies'])
    assert report['promotions_allowed'] is False
    changed = extra.copy()
    changed.loc[5,'close'] *= 1.01
    extended.write_text('\n'.join(raw[:-1])+'\n'+changed.to_csv(index=False, header=False))
    with pytest.raises(ValueError, match='history'):
        observer.observe(bundle, extended)


def test_observation_updates_retain_open_position_and_entry_fee(frozen_bundle, tmp_path, monkeypatch):
    observer, bundle, data = frozen_bundle
    raw = data.read_text().strip().splitlines()
    original = pd.read_csv(data)
    start = pd.Timestamp('2026-08-22 06:00')
    def known_scores(frame, **params):
        frame['activation_score'] = np.where(frame.time < start, 1000., -999.)
        return frame
    monkeypatch.setattr(observer, 'generate_signals', known_scores)
    # Source hashing covers files, not this deliberately injected test function.
    def capture(last):
        times = pd.date_range('2026-08-21 12:00', last, freq='6h')
        extra = pd.concat([original.iloc[[-2]]] * len(times), ignore_index=True)
        extra['time'] = times.as_unit('ns').astype('int64') // 10**9
        extra['close'] = np.where(times <= start, 100., 110.)
        extra['high'] = extra['close']
        path = tmp_path / 'position.csv'
        path.write_text('\n'.join(raw[:-1])+'\n'+extra.to_csv(index=False, header=False))
        return path
    monkeypatch.setattr(observer, 'now_utc', lambda: pd.Timestamp('2026-08-22 12:01', tz='UTC'))
    first = observer.observe(bundle, capture('2026-08-22 12:00'))['policies'][0]
    assert first['metrics']['open_at_end']
    assert first['metrics']['entries'] == 1
    monkeypatch.setattr(observer, 'now_utc', lambda: pd.Timestamp('2026-08-22 18:01', tz='UTC'))
    second = observer.observe(bundle, capture('2026-08-22 18:00'))['policies'][0]
    assert second['completed_trades'] == 1
    assert second['metrics']['pnl_pct'] == pytest.approx(9.)
