"""
dm_test_verify.py
Offline verification companion for DM_Test_EA.mq5 -- mirrors the MQL5
feature contract and Diebold-Mariano math exactly for consistency checks.
"""
import json
import numpy as np
from scipy import stats

NUM_FEATURES = 8  # must match MQL5 NUM_FEATURES contract


def load_manifest(path="dm_manifest.json"):
    with open(path, "r") as f:
        return json.load(f)


def build_feature_vector(close, atr, rsi, macd_main, macd_signal, ema, volume_real, i):
    """Mirrors BuildFeatureVector() in DM_Test_EA.mq5 bar-for-bar."""
    atr_now = max(atr[i], 1e-8)
    features = np.empty(NUM_FEATURES, dtype=np.float32)
    features[0] = (close[i] - close[i - 1]) / atr_now
    features[1] = (close[i - 1] - close[i - 2]) / atr_now
    features[2] = (close[i - 2] - close[i - 3]) / atr_now
    features[3] = atr_now / close[i]
    features[4] = rsi[i] / 100.0
    features[5] = (macd_main[i] - macd_signal[i]) / atr_now
    features[6] = (ema[i] - ema[i - 2]) / (2.0 * atr_now)
    features[7] = np.log1p(volume_real[i] / 1000.0)
    assert features.shape[0] == NUM_FEATURES, "feature contract mismatch"
    return features


def hac_variance(d, horizon):
    """Bartlett-kernel HAC variance -- mirrors DM_HACVariance() exactly."""
    n = len(d)
    mean = np.mean(d)
    lag_max = min(max(0, horizon - 1), n - 1)
    gamma0 = np.sum((d - mean) ** 2) / n
    var_sum = gamma0
    for k in range(1, lag_max + 1):
        w = 1.0 - k / horizon
        gamma_k = np.sum((d[k:] - mean) * (d[:-k] - mean)) / n
        var_sum += 2.0 * w * gamma_k
    return var_sum


def dm_test(err_a, err_b, horizon=1, loss="squared", min_sample_size=30, significance_alpha=0.10):
    """Mirrors DM_RunTest() -- HLN-corrected DM statistic and p-value.

    min_sample_size and significance_alpha are exposed as parameters (read
    from dm_manifest.json by the caller) rather than hardcoded, matching the
    MQL5 side, which now takes the same two values from DM_RunTest()'s
    signature instead of literal 30 / 0.10 constants.
    """
    err_a, err_b = np.asarray(err_a), np.asarray(err_b)

    # a horizon of zero or less makes both the truncation lag (h-1) and the
    # HLN correction meaningless, so reject it before touching the data
    if horizon <= 0:
        return {"verdict": "INVALID_HORIZON", "n": 0}

    d = (err_a ** 2 - err_b ** 2) if loss == "squared" else (np.abs(err_a) - np.abs(err_b))
    n = len(d)
    if n < min_sample_size:
        return {"verdict": "INSUFFICIENT_SAMPLE", "n": n}

    mean_d = np.mean(d)
    long_run_var = hac_variance(d, horizon)

    # a negative long-run variance is a numerically invalid HAC estimate
    # (the Bartlett-kernel sum is not guaranteed positive semi-definite in
    # finite samples) and must be reported as such, not folded into
    # "no significant difference"
    if long_run_var < 0:
        return {"verdict": "INVALID_VARIANCE", "n": n, "hac_variance": long_run_var}

    denom = long_run_var / n
    if denom < 1e-12:
        return {"verdict": "NO_SIGNIFICANT_DIFF", "n": n, "dm_stat": 0.0}

    dm_raw = mean_d / np.sqrt(denom)

    # Harvey, Leybourne & Newbold (1997) small-sample correction:
    # S* = S * sqrt[ (n + 1 - 2h + n^-1 h(h-1)) / n ]
    # the h(h-1) term is divided by n BEFORE being summed with (n+1-2h)
    correction_numerator = (n + 1 - 2 * horizon) + (horizon * (horizon - 1)) / n
    hln_factor = np.sqrt(correction_numerator / n)
    dm_hln = dm_raw * hln_factor
    pvalue = 2.0 * (1.0 - stats.t.cdf(abs(dm_hln), df=n - 1))

    if pvalue > significance_alpha:
        verdict = "NO_SIGNIFICANT_DIFF"
    else:
        verdict = "A_BETTER" if mean_d < 0 else "B_BETTER"

    return {
        "verdict": verdict, "n": n, "mean_diff": mean_d,
        "hac_variance": long_run_var, "dm_stat": dm_raw,
        "hln_stat": dm_hln, "pvalue": pvalue,
    }


def consistency_check(mql5_export_csv, horizon=1, min_sample_size=30, significance_alpha=0.10):
    """Cross-checks Python DM output against errA/errB exported from the EA's
    Files\\DMTest\\ directory to confirm feature-contract and math parity."""
    import pandas as pd
    df = pd.read_csv(mql5_export_csv)
    result = dm_test(df["err_a"].values, df["err_b"].values, horizon=horizon,
                      min_sample_size=min_sample_size, significance_alpha=significance_alpha)
    print(json.dumps(result, indent=2, default=float))
    return result


if __name__ == "__main__":
    manifest = load_manifest()
    print("Loaded manifest:", manifest)
