# retoor <retoor@molodetz.nl>

import json
import shutil
from datetime import datetime, timezone

import pytest
from starlette.websockets import WebSocketDisconnect

from devplacepy.config import DBAPI_DIR
from devplacepy.services.manager import service_manager


def test_query_requires_auth(client):
    assert client.post("/dbapi/query", json={"sql": "SELECT 1"}).status_code == 403


def test_select_runs(client, auth):
    response = client.post("/dbapi/query", json={"sql": "SELECT 1 AS n"}, headers=auth)
    assert response.status_code == 200
    body = response.json()
    assert body["valid"] is True
    assert body["rows"] == [{"n": 1}]


def test_non_select_is_409(client, auth):
    response = client.post(
        "/dbapi/query", json={"sql": "DELETE FROM posts"}, headers=auth
    )
    assert response.status_code == 409
    assert response.json()["statement_type"] == "delete"


def test_invalid_sql_is_400(client, auth):
    response = client.post(
        "/dbapi/query", json={"sql": "SELECT * FROM no_such_table_zzz"}, headers=auth
    )
    assert response.status_code == 400


def test_suspicious_select_is_flagged(client, auth):
    response = client.post(
        "/dbapi/query", json={"sql": "SELECT * FROM users"}, headers=auth
    )
    assert response.status_code == 200
    assert response.json()["suspicious"]


def test_async_enqueue_and_status(client, auth):
    enqueued = client.post(
        "/dbapi/query/async", json={"sql": "SELECT uid FROM users LIMIT 3"}, headers=auth
    )
    assert enqueued.status_code == 200
    uid = enqueued.json()["uid"]
    assert enqueued.json()["ws_url"].endswith("/ws")
    assert client.get(f"/dbapi/query/{uid}", headers=auth).status_code == 200


def test_async_non_select_is_409(client, auth):
    response = client.post(
        "/dbapi/query/async", json={"sql": "UPDATE users SET role = 1"}, headers=auth
    )
    assert response.status_code == 409


def test_ws_unknown_job_is_closed(client, auth):
    with client.websocket_connect("/dbapi/query/not-a-real-uid/ws") as ws:
        with pytest.raises(WebSocketDisconnect) as info:
            ws.receive_json()
    assert info.value.code == 1008


def test_ws_non_owner_retries(client, auth):
    service_manager.set_lock_owner(False)
    try:
        with client.websocket_connect("/dbapi/query/anything/ws") as ws:
            with pytest.raises(WebSocketDisconnect) as info:
                ws.receive_json()
        assert info.value.code == 4013
    finally:
        service_manager.set_lock_owner(True)


def test_result_route_serves_the_persisted_rows(client, auth):
    enqueued = client.post(
        "/dbapi/query/async", json={"sql": "SELECT 1 AS n"}, headers=auth
    )
    uid = enqueued.json()["uid"]
    output_dir = DBAPI_DIR / uid
    output_dir.mkdir(parents=True, exist_ok=True)
    result = {
        "sql": "SELECT 1 AS n",
        "row_count": 1,
        "truncated": False,
        "suspicious": [],
        "rows": [{"n": 1}],
    }
    (output_dir / "result.json").write_text(json.dumps(result), encoding="utf-8")
    try:
        response = client.get(f"/dbapi/query/{uid}/result", headers=auth)
        assert response.status_code == 200, response.text
        assert response.json()["rows"] == [{"n": 1}]
    finally:
        shutil.rmtree(output_dir, ignore_errors=True)


def test_result_route_404s_without_a_written_result(client, auth):
    enqueued = client.post(
        "/dbapi/query/async", json={"sql": "SELECT 1 AS n"}, headers=auth
    )
    uid = enqueued.json()["uid"]
    response = client.get(f"/dbapi/query/{uid}/result", headers=auth)
    assert response.status_code == 404


def test_result_route_rejects_a_traversal_uid(client, auth):
    from devplacepy.database import get_table

    evil_uid = ".."
    now = datetime.now(timezone.utc).isoformat()
    get_table("jobs").insert(
        {
            "uid": evil_uid,
            "kind": "dbquery",
            "status": "done",
            "owner_kind": "user",
            "owner_id": "traversal-owner",
            "preferred_name": "",
            "payload": "{}",
            "result": "{}",
            "error": "",
            "retry_count": 0,
            "created_at": now,
            "started_at": now,
            "completed_at": now,
            "updated_at": now,
            "duration_ms": 0,
            "last_accessed_at": "",
            "expires_at": "",
            "bytes_in": 0,
            "bytes_out": 0,
            "item_count": 0,
        }
    )
    escape_target = DBAPI_DIR.parent / "result.json"
    try:
        response = client.get(f"/dbapi/query/{evil_uid}/result", headers=auth)
        assert response.status_code in (400, 404), response.text
        assert not escape_target.exists()
    finally:
        get_table("jobs").delete(uid=evil_uid)
