import asyncio
import importlib
import os
import sys
import threading
from pathlib import Path

from fastapi.testclient import TestClient

os.environ["JOB_BACKEND"] = "memory"

BACKEND_DIR = Path(__file__).resolve().parents[1]
if str(BACKEND_DIR) not in sys.path:
    sys.path.insert(0, str(BACKEND_DIR))

import job_handlers  # type: ignore
import job_system  # type: ignore
import risk_control  # type: ignore
import session_store  # type: ignore
import audit_store  # type: ignore

main = importlib.import_module("main")

API_KEY_HEADERS = {"X-API-Key": "your-secret-key-here"}


def setup_function():
    job_system.reset_job_manager()
    risk_control.reset_risk_controller()
    session_store.reset_session_store()
    audit_store.reset_audit_store()
    main._handlers_registered = False


def _completion_payload():
    return {
        "prefix": "hello",
        "suffix": "",
        "languageId": "markdown",
        "model_thinking": "low",
        "privacy_mode": True,
    }


def test_cancel_endpoint_cancels_running_task(monkeypatch):
    started = threading.Event()
    cancelled = threading.Event()

    async def fake_call_ollama(*args, **kwargs):
        started.set()
        try:
            while True:
                await asyncio.sleep(0.05)
        except asyncio.CancelledError:
            cancelled.set()
            raise

    monkeypatch.setattr(job_handlers, "call_ollama", fake_call_ollama)
    request_id = "req-cancel-1"

    with TestClient(main.app) as client:
        response_box = {}

        def send_completion():
            with client.stream(
                "POST",
                "/v1/completions",
                headers={**API_KEY_HEADERS, "X-Request-Id": request_id},
                json=_completion_payload(),
            ) as response:
                response_box["status_code"] = response.status_code
                response_box["body"] = "".join(response.iter_text())

        completion_thread = threading.Thread(target=send_completion, daemon=True)
        completion_thread.start()

        assert started.wait(timeout=2.0)

        cancel_response = client.post(
            "/v1/completions/cancel",
            headers=API_KEY_HEADERS,
            json={"request_id": request_id, "reason": "superseded"},
        )
        assert cancel_response.status_code == 200
        assert cancel_response.json() == {"cancelled": True, "status": "ok"}

        completion_thread.join(timeout=5.0)
        assert not completion_thread.is_alive()
        assert cancelled.wait(timeout=2.0)
        assert "event: cancelled" in response_box["body"]


class FakeRedis:
    def __init__(self):
        self.acks = []

    async def xack(self, *args):
        self.acks.append(args)

    async def hincrby(self, key, field, amount):
        return 0


class FakeManager:
    def __init__(self):
        self.redis = FakeRedis()
        self.statuses = {}

    async def get_status(self, job_id):
        return self.statuses.get(job_id)

    async def _set_state(self, job_id, state):
        self.statuses[job_id] = state

    async def _metrics(self, job_type):
        return {"queued_count": 0, "running_count": 0}

    async def _emit_event(self, job_id, event, data):
        self.statuses[job_id]["event"] = event

    def _metrics_key(self, job_type):
        return f"metrics:{job_type}"

    def _state_key(self, job_id):
        return f"state:{job_id}"


async def _run_cancelled_after_handler(manager, job_type):
    worker = job_system.RedisWorker(manager)
    await worker._run_message(
        job_type,
        "queue",
        "group",
        "msg-1",
        {"job_id": "job-1"},
        asyncio.Semaphore(1),
    )


def test_redis_worker_acks_when_handler_returns_cancelled_state():
    async def handler(payload, emit, is_cancelled):
        return {"ok": True}

    async def coro():
        manager = FakeManager()
        manager.handlers = {"completion": handler}
        manager.statuses["job-1"] = {
            "request_id": "req-1",
            "type": "completion",
            "status": "running",
            "created_at": 1,
        }
        await _run_cancelled_after_handler(manager, "completion")
        assert manager.redis.acks == [("queue", "group", "msg-1")]

    asyncio.run(coro())


def test_cancel_not_found():
    with TestClient(main.app) as client:
        response = client.post(
            "/v1/completions/cancel",
            headers=API_KEY_HEADERS,
            json={"request_id": "missing", "reason": "abort"},
        )
    assert response.status_code == 200
    assert response.json() == {"cancelled": False, "status": "not_found"}
