from fastapi.testclient import TestClient
from jarvis_finance.api.dependencies import get_db
from jarvis_finance.api.main import create_app
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import apply_migrations


def db():
    conn = connect_memory()
    apply_migrations(conn)
    return conn


def payload(**changes):
    base = {
        "base_currency": "CHF", "effective_from": "2026-07-23", "horizon": "long",
        "objective": "Long term", "max_crypto_pct": "20",
        "allocations": [
            {"asset_class": "cash", "target_pct": "60", "lower_pct": "50", "upper_pct": "70"},
            {"asset_class": "equity", "target_pct": "40", "lower_pct": "30", "upper_pct": "50"},
        ],
    }
    base.update(changes)
    return base


def client_for(conn):
    app = create_app(write_mode="test")
    app.dependency_overrides[get_db] = lambda: conn
    return TestClient(app)


def confirm(client, data):
    preview = client.post("/api/portfolio/policy/preview", json=data)
    assert preview.status_code == 200 and preview.json()["valid"]
    return client.post("/api/portfolio/policy/confirm", json={**data, "preview_id": preview.json()["preview_id"], "confirm": True})


def test_preview_is_read_only_confirm_is_idempotent_audited_and_history_is_immutable():
    conn = db()
    client = client_for(conn)
    assert client.get("/api/portfolio/policy").json() == {"configured": False, "policy": None}
    before = conn.execute("SELECT COUNT(*) FROM portfolio_policies").fetchone()[0]
    preview = client.post("/api/portfolio/policy/preview", json=payload())
    assert preview.status_code == 200 and preview.json()["valid"]
    assert conn.execute("SELECT COUNT(*) FROM portfolio_policies").fetchone()[0] == before
    first = confirm(client, payload())
    assert first.status_code == 200
    first_json = first.json()
    second = confirm(client, payload())
    assert second.status_code == 200 and second.json()["idempotent"] is True
    assert conn.execute("SELECT COUNT(*) FROM portfolio_policies").fetchone()[0] == 1
    audit = conn.execute("SELECT action, confirmed, new_values_json FROM audit_log WHERE audit_id=?", (first_json["audit_id"],)).fetchone()
    assert audit["action"] == "portfolio_policy_confirmed" and audit["confirmed"] == 1 and '"version": 1' in audit["new_values_json"]
    with __import__("pytest").raises(Exception, match="immutable"):
        conn.execute("UPDATE portfolio_policies SET objective='changed' WHERE policy_id=?", (first_json["policy_id"],))
    with __import__("pytest").raises(Exception, match="cannot be deleted"):
        conn.execute("DELETE FROM portfolio_policy_allocations WHERE policy_id=?", (first_json["policy_id"],))
    assert client.get("/api/portfolio/policy/history").json()[0]["version"] == 1


def test_new_confirmation_creates_a_linked_version_and_conservative_evaluation():
    conn = db()
    client = client_for(conn)
    first = confirm(client, payload()).json()
    second_data = payload(effective_from="2026-08-01", allocations=[
        {"asset_class": "cash", "target_pct": "50", "lower_pct": "40", "upper_pct": "60"},
        {"asset_class": "equity", "target_pct": "40", "lower_pct": "30", "upper_pct": "50"},
        {"asset_class": "crypto", "target_pct": "10", "lower_pct": "0", "upper_pct": "20"},
    ])
    second = confirm(client, second_data).json()
    assert second["version"] == 2 and second["policy_id"] != first["policy_id"]
    history = client.get("/api/portfolio/policy/history").json()
    assert [item["version"] for item in history] == [2, 1]
    assert history[0]["previous_policy_id"] == first["policy_id"] and history[0]["is_active"] is True and history[1]["is_active"] is False
    evaluation = client.get("/api/portfolio/policy/evaluation").json()
    assert evaluation["configured"] is True and evaluation["data_quality_status"] == "partial"
    assert {row["status"] for row in evaluation["rows"]} == {"not_assessable"}


def test_confirm_fails_closed_for_missing_confirmation_preview_mismatch_and_invalid_bounds():
    conn = db()
    client = client_for(conn)
    data = payload()
    preview = client.post("/api/portfolio/policy/preview", json=data).json()
    missing = client.post("/api/portfolio/policy/confirm", json={**data, "preview_id": preview["preview_id"], "confirm": False})
    assert missing.status_code == 400 and "Bestätigung" in missing.json()["detail"]
    mismatch = client.post("/api/portfolio/policy/confirm", json={**data, "preview_id": "wrong", "confirm": True})
    assert mismatch.status_code == 400 and "Vorschau" in mismatch.json()["detail"]
    bad = payload(allocations=[{"asset_class": "cash", "target_pct": "101", "lower_pct": "90", "upper_pct": "110"}])
    response = client.post("/api/portfolio/policy/preview", json=bad)
    assert response.status_code == 200 and response.json()["valid"] is False
    assert conn.execute("SELECT COUNT(*) FROM portfolio_policies").fetchone()[0] == 0
