import os

import numpy as np
import pytest

from hunt import checks

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


def table(cols, n, rng, **kw):
    """Build (columns, rows) of strings from arrays."""
    arrays = [kw[c] for c in cols]
    rows = [[str(a[i]) for a in arrays] for i in range(n)]
    return cols, rows


def test_read_csv_handles_quotes_and_short_rows():
    text = 'a,"b, c",d\n1,"x ""q""",3\n4,5\n\n7,8,9,10\n'
    cols, rows = checks.read_csv(text)
    assert cols == ["a", "b, c", "d"]
    assert rows == [["1", 'x "q"', "3"], ["4", "5", ""], ["7", "8", "9"]]
    cols, rows = checks.read_csv("a\tb\n1\t2\n")
    assert cols == ["a", "b"] and rows == [["1", "2"]]


def test_dates_and_numbers():
    assert checks.days_from_civil(1970, 1, 1) == 0
    assert checks.days_from_civil(2000, 3, 1) == 11017
    assert checks.parse_date("2024-02-29T12:00") == pytest.approx(checks.days_from_civil(2024, 2, 29) + 0.5)
    assert checks.parse_date("2024-13-01") is None
    assert checks.parse_num("1,234.5") == 1234.5 and checks.parse_num("abc") is None


def test_demo_file_trips_the_planted_shortcuts():
    with open(os.path.join(ROOT, "web", "demo.csv"), encoding="utf-8") as f:
        cols, rows = checks.read_csv(f.read())
    r = checks.analyze(cols, rows, "left")
    ids = {c["id"]: c["severity"] for c in r["checks"]}
    assert ids["id:customer_id"] == "high"
    assert ids["leak:refund_amount"] == "high"
    assert ids["time"] == "high"
    assert ids["dejavu"] in ("high", "medium")
    assert r["positive"] == "yes" and r["time_column"] == "signup_date"


def test_clean_data_mostly_passes():
    rng = np.random.default_rng(0)
    n = 400
    x1, x2, x3 = rng.normal(size=n), rng.normal(size=n), rng.normal(size=n)
    y = (x1 + 0.5 * x2 + rng.normal(size=n) > 0).astype(int)
    cols, rows = table(["x1", "x2", "x3", "y"], n, rng, x1=np.round(x1, 3), x2=np.round(x2, 3), x3=np.round(x3, 3), y=y)
    r = checks.analyze(cols, rows, "y")
    sev = {c["id"]: c["severity"] for c in r["checks"]}
    assert all(s in ("ok", "low") for s in sev.values()), sev
    assert r["headline"]["acc"] > 0.7


def test_sorted_ids_and_leaky_column_are_caught():
    rng = np.random.default_rng(1)
    n = 300
    x = rng.normal(size=n)
    y = (x + rng.normal(size=n) > 0).astype(int)
    ids = np.empty(n, dtype=int)
    ids[np.argsort(y, kind="stable")] = np.arange(n)
    leak = y * 10 + rng.normal(size=n)
    cols, rows = table(["id", "x", "after", "y"], n, rng, id=ids, x=np.round(x, 3), after=np.round(leak, 2), y=y)
    r = checks.analyze(cols, rows, "y")
    sev = {c["id"]: c["severity"] for c in r["checks"]}
    assert sev["id:id"] == "high" and sev["leak:after"] == "high"


def test_duplicates_and_drift_are_caught():
    rng = np.random.default_rng(2)
    n = 300
    X = rng.normal(size=(n, 4))
    y = (X[:, 0] + rng.normal(size=n) > 0).astype(int)
    X = np.vstack([X, X[:150]])                    # half the file again
    y = np.concatenate([y, y[:150]])
    cols, rows = table(["a", "b", "c", "d", "y"], len(y), rng, a=X[:, 0], b=X[:, 1], c=X[:, 2], d=X[:, 3], y=y)
    r = checks.analyze(cols, rows, "y")
    sev = {c["id"]: c["severity"] for c in r["checks"]}
    assert sev["dejavu"] in ("high", "medium")

    day = np.arange(n)
    y2 = (rng.random(n) < np.where(day < 200, 0.1, 0.8)).astype(int)
    cols, rows = table(["day", "a", "y"], n, rng, day=day, a=np.round(rng.normal(size=n), 3), y=y2)
    r = checks.analyze(cols, rows, "y")
    sev = {c["id"]: c["severity"] for c in r["checks"]}
    assert r["time_column"] == "day" and sev["time"] == "high"


def test_target_rules():
    rng = np.random.default_rng(3)
    n = 60
    cols, rows = table(["x", "y"], n, rng, x=rng.normal(size=n), y=rng.choice(["a", "b", "c"], n))
    r = checks.analyze(cols, rows, "y")
    assert r["positive_how"] == "most common class against the rest"
    with pytest.raises(ValueError):
        checks.analyze(cols, rows[:20], "y")
    with pytest.raises(ValueError):
        checks.analyze(cols, rows, "z")
    cols, rows = table(["x", "y"], n, rng, x=rng.normal(size=n), y=np.arange(n))
    with pytest.raises(ValueError):
        checks.analyze(cols, rows, "y")
