//! Property-based tests for ZigZag correctness invariants.

use proptest::prelude::*;
use qta::zigzag::{BarInput, ConfirmationStatus, PivotKind, ZigZagConfig, ZigZagState};

const SCALE: i64 = 100_000_000;

fn fp(value: f64) -> i64 {
    (value * SCALE as f64).round() as i64
}

fn bar_sequence_strategy(n_bars: usize) -> impl Strategy<Value = Vec<BarInput>> {
    let start_price = 50_000.0_f64;
    let bar_range = 125.0;

    prop::collection::vec(-200.0..200.0_f64, n_bars).prop_map(move |steps| {
        let mut bars = Vec::with_capacity(n_bars);
        let mut mid = start_price;

        for (i, step) in steps.into_iter().enumerate() {
            mid += step;
            mid = mid.clamp(40_000.0, 60_000.0);
            let half = bar_range / 2.0;
            let high = mid + half;
            let low = mid - half;
            let close = (mid + step * 0.3).clamp(low, high);

            bars.push(BarInput {
                index: i,
                timestamp_us: (i as i64) * 60_000_000,
                high: fp(high),
                low: fp(low),
                close: fp(close),
                duration_us: Some(60_000_000),
            });
        }
        bars
    })
}

fn run_zigzag(bars: &[BarInput], reversal_depth: f64) -> ZigZagState {
    let config = ZigZagConfig::new(reversal_depth, reversal_depth * 0.3, 250).unwrap();
    let mut state = ZigZagState::new(config);
    for bar in bars {
        state.process_bar(bar);
    }
    state
}

proptest! {
    #[test]
    fn no_repaint_invariant(bars in bar_sequence_strategy(200)) {
        let config = ZigZagConfig::new(3.0, 1.0, 250).unwrap();
        let mut state = ZigZagState::new(config);
        let mut snapshot: Vec<(usize, i64, PivotKind)> = Vec::new();

        for bar in &bars {
            state.process_bar(bar);
            let current = state.confirmed_pivots();
            for (i, &(idx, price, kind)) in snapshot.iter().enumerate() {
                prop_assert_eq!(current[i].bar_index, idx);
                prop_assert_eq!(current[i].price, price);
                prop_assert_eq!(current[i].kind, kind);
            }
            snapshot = current.iter().map(|p| (p.bar_index, p.price, p.kind)).collect();
        }
    }

    #[test]
    fn alternation_invariant(bars in bar_sequence_strategy(200)) {
        let state = run_zigzag(&bars, 3.0);
        for w in state.confirmed_pivots().windows(2) {
            prop_assert_ne!(w[0].kind, w[1].kind);
        }
    }

    #[test]
    fn monotonic_generation(bars in bar_sequence_strategy(200)) {
        let state = run_zigzag(&bars, 3.0);
        let mut prev_gen = 0_u64;
        for pivot in state.confirmed_pivots() {
            if let ConfirmationStatus::Confirmed { generation, .. } = pivot.status {
                prop_assert!(generation > prev_gen);
                prev_gen = generation;
            }
        }
    }

    #[test]
    fn pending_after_init(bars in bar_sequence_strategy(200)) {
        let config = ZigZagConfig::new(3.0, 1.0, 250).unwrap();
        let mut state = ZigZagState::new(config);
        let mut initialized = false;

        for bar in &bars {
            let output = state.process_bar(bar);
            if output.newly_confirmed.is_some() {
                initialized = true;
            }
            if initialized {
                prop_assert!(state.pending().is_some());
            }
        }
    }

    #[test]
    fn monotonic_bar_indices(bars in bar_sequence_strategy(200)) {
        let state = run_zigzag(&bars, 3.0);
        for w in state.confirmed_pivots().windows(2) {
            prop_assert!(w[1].bar_index > w[0].bar_index);
        }
    }

    #[test]
    fn confirmed_at_after_pivot(bars in bar_sequence_strategy(200)) {
        let state = run_zigzag(&bars, 3.0);
        for pivot in state.confirmed_pivots() {
            if let ConfirmationStatus::Confirmed { confirmed_at_bar, .. } = pivot.status {
                prop_assert!(confirmed_at_bar >= pivot.bar_index);
            }
        }
    }
}

#[test]
fn reversal_depth_sweep() {
    let mut bars = Vec::new();
    let mut price = 50_000.0;
    let directions = [1.0, 1.0, 1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, -1.0];

    for i in 0..500 {
        let dir = directions[i % directions.len()];
        price += dir * 50.0 + (i as f64 * 0.1).sin() * 20.0;
        price = price.clamp(45_000.0, 55_000.0);
        bars.push(BarInput {
            index: i,
            timestamp_us: (i as i64) * 60_000_000,
            high: fp(price + 62.5),
            low: fp(price - 62.5),
            close: fp(price),
            duration_us: Some(60_000_000),
        });
    }

    let depths = [1.5, 2.0, 3.0, 5.0, 8.0];
    let mut prev_count = usize::MAX;

    for &n in &depths {
        let state = run_zigzag(&bars, n);
        let count = state.confirmed_pivots().len();
        assert!(
            count <= prev_count,
            "N={n} gave {count} pivots, prev gave {prev_count}"
        );
        prev_count = count;
    }
}
