import asyncio
import json
import os
from typing import Any, Dict, Optional

from redis.asyncio import Redis

REDIS_URL = os.getenv("REDIS_URL", "redis://redis:6379/0")
JOB_KEY_PREFIX = "daarion:jobs"
QUEUE_KEY = "daarion:jobs:queue"
JOB_TTL_SECONDS = int(os.getenv("DAARION_JOB_TTL_SECONDS", str(72 * 3600)))

_redis: Optional[Redis] = None


def _job_key(job_id: str) -> str:
    return f"{JOB_KEY_PREFIX}:{job_id}"


async def redis_client() -> Redis:
    global _redis
    if _redis is None:
        _redis = Redis.from_url(REDIS_URL, decode_responses=True)
    return _redis


async def close_redis() -> None:
    global _redis
    if _redis is not None:
        await _redis.close()
        _redis = None


async def create_job(job_id: str, payload: Dict[str, Any]) -> None:
    r = await redis_client()
    key = _job_key(job_id)
    await r.set(key, json.dumps(payload, ensure_ascii=False), ex=JOB_TTL_SECONDS)


async def get_job(job_id: str) -> Optional[Dict[str, Any]]:
    r = await redis_client()
    raw = await r.get(_job_key(job_id))
    if not raw:
        return None
    try:
        return json.loads(raw)
    except json.JSONDecodeError:
        return None


async def update_job(job_id: str, patch: Dict[str, Any]) -> Optional[Dict[str, Any]]:
    current = await get_job(job_id)
    if not current:
        return None
    current.update(patch)
    await create_job(job_id, current)
    return current


async def enqueue_job(job_id: str) -> None:
    r = await redis_client()
    await r.lpush(QUEUE_KEY, job_id)


async def dequeue_job(block_seconds: int = 5) -> Optional[str]:
    r = await redis_client()
    result = await r.brpop(QUEUE_KEY, timeout=block_seconds)
    if not result:
        return None
    _, job_id = result
    return job_id


async def wait_for_redis(timeout_seconds: int = 30) -> None:
    deadline = asyncio.get_running_loop().time() + timeout_seconds
    while True:
        try:
            r = await redis_client()
            await r.ping()
            return
        except Exception:
            if asyncio.get_running_loop().time() >= deadline:
                raise
            await asyncio.sleep(1)
