"""Generic persistent session store for any communication channel."""

import json
import re
from datetime import datetime, timezone
from pathlib import Path

# Characters unsafe in filenames — replace with underscores
_UNSAFE_RE = re.compile(r"[^a-zA-Z0-9+\-_]")


def _safe_filename(key: str) -> str:
    """Sanitize a session key for use as a filename."""
    return _UNSAFE_RE.sub("_", key)


# Channel-specific external_message tag formats
_TAG_FORMATS: dict[str, str] = {
    "telegram": '<external_message source="telegram" chat_id="{key}"{extra}>',
    "email": '<external_message source="email" sender="{key}">',
    "sms": '<external_message source="sms" phone="{key}">',
}


class ChannelSessionStore:
    """Append-only JSONL log per session for any channel (telegram, email, sms)."""

    def __init__(self, fliiq_dir: Path, channel: str):
        self._dir = fliiq_dir / f"{channel}_sessions"
        self._dir.mkdir(parents=True, exist_ok=True)
        self._channel = channel

    def _log_path(self, session_key: str, thread_id: int | None = None) -> Path:
        safe = _safe_filename(session_key)
        if thread_id:
            return self._dir / f"{safe}_thread_{thread_id}.jsonl"
        return self._dir / f"{safe}.jsonl"

    def log_message(
        self,
        session_key: str,
        role: str,
        text: str,
        thread_id: int | None = None,
    ) -> None:
        """Append a message to the session's persistent log."""
        entry = {
            "role": role,
            "text": text,
            "ts": datetime.now(timezone.utc).isoformat(),
        }
        path = self._log_path(session_key, thread_id)
        with open(path, "a") as f:
            f.write(json.dumps(entry) + "\n")

    def load_recent(
        self,
        session_key: str,
        thread_id: int | None = None,
        max_messages: int = 20,
        max_age_hours: int = 24,
    ) -> list[dict]:
        """Load recent messages as OpenAI-format message dicts.

        Returns up to *max_messages* within the last *max_age_hours*.
        Inbound (user) messages are wrapped in <external_message> tags.
        """
        path = self._log_path(session_key, thread_id)
        if not path.exists():
            return []

        cutoff = datetime.now(timezone.utc).timestamp() - (max_age_hours * 3600)
        entries: list[dict] = []

        with open(path) as f:
            for line in f:
                line = line.strip()
                if not line:
                    continue
                try:
                    entry = json.loads(line)
                    ts = datetime.fromisoformat(entry["ts"]).timestamp()
                    if ts >= cutoff:
                        entries.append(entry)
                except (json.JSONDecodeError, KeyError, ValueError):
                    continue

        recent = entries[-max_messages:] if entries else []

        messages: list[dict] = []
        for entry in recent:
            role = entry["role"]
            text = entry["text"]
            if role == "user":
                content = self._wrap_user_message(session_key, text, thread_id)
                messages.append({"role": "user", "content": content})
            else:
                messages.append({"role": "assistant", "content": text})

        return messages

    def _wrap_user_message(
        self, session_key: str, text: str, thread_id: int | None = None,
    ) -> str:
        """Wrap inbound user message with channel-specific external_message tag."""
        extra = f' message_thread_id="{thread_id}"' if thread_id else ""
        fmt = _TAG_FORMATS.get(self._channel)
        if fmt:
            open_tag = fmt.format(key=session_key, extra=extra)
        else:
            open_tag = f'<external_message source="{self._channel}" id="{session_key}">'
        return f"{open_tag}\n{text}\n</external_message>"
