import json
import os
import time
import uuid
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from redis.asyncio import Redis

REDIS_URL = os.getenv("REDIS_URL", "redis://redis:6379/0")
REMINDER_PREFIX = "daarion:reminders"
REMINDER_BY_ID = f"{REMINDER_PREFIX}:by_id"
REMINDER_SCHEDULE = f"{REMINDER_PREFIX}:schedule"
REMINDER_TTL_SECONDS = int(os.getenv("DAARION_REMINDER_TTL_SECONDS", str(30 * 24 * 3600)))

_redis: Optional[Redis] = None


@dataclass
class Reminder:
    reminder_id: str
    agent_id: str
    chat_id: str
    user_id: str
    text: str
    due_ts: int
    created_at: str

    def to_dict(self) -> Dict[str, Any]:
        return {
            "reminder_id": self.reminder_id,
            "agent_id": self.agent_id,
            "chat_id": self.chat_id,
            "user_id": self.user_id,
            "text": self.text,
            "due_ts": self.due_ts,
            "created_at": self.created_at,
        }


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


def _iso_now() -> str:
    return datetime.now(timezone.utc).isoformat()


def _iso_from_ts(ts: int) -> str:
    return datetime.fromtimestamp(ts, tz=timezone.utc).isoformat()


async def create_reminder(agent_id: str, chat_id: str, user_id: str, text: str, due_ts: int) -> Dict[str, Any]:
    reminder = Reminder(
        reminder_id=f"rem_{uuid.uuid4().hex[:16]}",
        agent_id=agent_id,
        chat_id=str(chat_id),
        user_id=str(user_id),
        text=text.strip(),
        due_ts=int(due_ts),
        created_at=_iso_now(),
    )

    r = await redis_client()
    key = f"{REMINDER_BY_ID}:{reminder.reminder_id}"
    payload = json.dumps(reminder.to_dict(), ensure_ascii=False)

    await r.set(key, payload, ex=REMINDER_TTL_SECONDS)
    await r.zadd(REMINDER_SCHEDULE, {reminder.reminder_id: float(reminder.due_ts)})

    result = reminder.to_dict()
    result["due_at"] = _iso_from_ts(reminder.due_ts)
    return result


async def list_reminders(agent_id: str, chat_id: str, user_id: str, limit: int = 10) -> List[Dict[str, Any]]:
    r = await redis_client()
    now_ts = int(time.time())
    ids = await r.zrangebyscore(REMINDER_SCHEDULE, min=now_ts - 365 * 24 * 3600, max="+inf", start=0, num=max(1, limit * 5))

    out: List[Dict[str, Any]] = []
    for reminder_id in ids:
        raw = await r.get(f"{REMINDER_BY_ID}:{reminder_id}")
        if not raw:
            continue
        try:
            item = json.loads(raw)
        except json.JSONDecodeError:
            continue
        if item.get("agent_id") != agent_id:
            continue
        if str(item.get("chat_id")) != str(chat_id):
            continue
        if str(item.get("user_id")) != str(user_id):
            continue
        item["due_at"] = _iso_from_ts(int(item.get("due_ts", 0)))
        out.append(item)
        if len(out) >= limit:
            break
    return out


async def cancel_reminder(reminder_id: str, agent_id: str, chat_id: str, user_id: str) -> bool:
    r = await redis_client()
    key = f"{REMINDER_BY_ID}:{reminder_id}"
    raw = await r.get(key)
    if not raw:
        return False
    try:
        item = json.loads(raw)
    except json.JSONDecodeError:
        return False

    if item.get("agent_id") != agent_id or str(item.get("chat_id")) != str(chat_id) or str(item.get("user_id")) != str(user_id):
        return False

    await r.delete(key)
    await r.zrem(REMINDER_SCHEDULE, reminder_id)
    return True


async def pop_due_reminders(limit: int = 20) -> List[Dict[str, Any]]:
    r = await redis_client()
    now_ts = int(time.time())
    ids = await r.zrangebyscore(REMINDER_SCHEDULE, min="-inf", max=now_ts, start=0, num=max(1, limit))
    out: List[Dict[str, Any]] = []

    for reminder_id in ids:
        removed = await r.zrem(REMINDER_SCHEDULE, reminder_id)
        if removed == 0:
            continue
        raw = await r.get(f"{REMINDER_BY_ID}:{reminder_id}")
        if not raw:
            continue
        await r.delete(f"{REMINDER_BY_ID}:{reminder_id}")
        try:
            item = json.loads(raw)
            item["due_at"] = _iso_from_ts(int(item.get("due_ts", now_ts)))
            out.append(item)
        except json.JSONDecodeError:
            continue

    return out
