from __future__ import annotations

import json
import os
import re
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Callable

from card_store import NANOBOT_WORKSPACE

NANOBOT_RUNTIME_WORKSPACE = Path(
    os.getenv(
        "NANOBOT_RUNTIME_WORKSPACE",
        str(
            (NANOBOT_WORKSPACE / "workspace")
            if (NANOBOT_WORKSPACE / "workspace").exists()
            else NANOBOT_WORKSPACE
        ),
    )
).expanduser()
SESSIONS_DIR = NANOBOT_RUNTIME_WORKSPACE / "sessions"
_SESSION_CHAT_ID_PATTERN = re.compile(r"^[a-zA-Z0-9_-]{1,128}$")


def ensure_session_store_dirs() -> None:
    SESSIONS_DIR.mkdir(parents=True, exist_ok=True)


def _normalize_session_chat_id(raw: str) -> str:
    chat_id = raw.strip()
    return chat_id if _SESSION_CHAT_ID_PATTERN.fullmatch(chat_id) else ""


def _is_web_session_chat_id(chat_id: str) -> bool:
    return chat_id == "web" or chat_id.startswith("web-")


def _session_key(chat_id: str) -> str:
    return f"api:{chat_id}"


def _session_path(chat_id: str) -> Path:
    return SESSIONS_DIR / f"api_{chat_id}.jsonl"


def _session_summary_from_messages(
    chat_id: str,
    *,
    created_at: str,
    updated_at: str,
    metadata: dict[str, Any],
    messages: list[dict[str, Any]],
) -> dict[str, Any]:
    title = str(metadata.get("title", "")).strip()
    preview = ""

    for message in messages:
        if message.get("role") == "user":
            text = str(message.get("content", "")).strip()
            if text:
                if not title:
                    title = text
                break

    for message in reversed(messages):
        role = str(message.get("role", "")).strip()
        text = str(message.get("content", "")).strip()
        if role in {"user", "assistant", "system"} and text:
            preview = text
            break

    if not title:
        title = "New conversation"

    return {
        "chat_id": chat_id,
        "key": _session_key(chat_id),
        "title": title[:120],
        "preview": preview[:240],
        "created_at": created_at,
        "updated_at": updated_at,
        "message_count": len(messages),
    }


def _load_session_file(chat_id: str) -> tuple[dict[str, Any], list[dict[str, Any]]] | None:
    ensure_session_store_dirs()
    path = _session_path(chat_id)
    if not path.exists():
        return None

    metadata: dict[str, Any] = {
        "key": _session_key(chat_id),
        "created_at": datetime.now(timezone.utc).isoformat(),
        "updated_at": datetime.now(timezone.utc).isoformat(),
        "metadata": {},
    }
    messages: list[dict[str, Any]] = []

    try:
        with path.open(encoding="utf-8") as handle:
            for raw_line in handle:
                line = raw_line.strip()
                if not line:
                    continue
                try:
                    payload = json.loads(line)
                except json.JSONDecodeError:
                    continue
                if not isinstance(payload, dict):
                    continue
                if payload.get("_type") == "metadata":
                    metadata = payload
                    continue
                messages.append(payload)
    except OSError:
        return None

    return metadata, messages


def _serialize_session_messages(messages: list[dict[str, Any]]) -> list[dict[str, str]]:
    rows: list[dict[str, str]] = []
    for message in messages:
        role = str(message.get("role", "")).strip().lower()
        content = str(message.get("content", "")).strip()
        timestamp = str(message.get("timestamp", "")).strip()
        if role not in {"user", "assistant", "tool", "system"}:
            continue
        if not content:
            continue
        rows.append(
            {
                "role": "nanobot" if role == "assistant" else role,
                "text": content,
                "timestamp": timestamp or datetime.now(timezone.utc).isoformat(),
            }
        )
    return rows


def _load_session_summary(chat_id: str) -> dict[str, Any] | None:
    loaded = _load_session_file(chat_id)
    if loaded is None:
        return None
    metadata, messages = loaded
    meta_dict = metadata.get("metadata", {})
    return _session_summary_from_messages(
        chat_id,
        created_at=str(metadata.get("created_at", "")),
        updated_at=str(metadata.get("updated_at", "")),
        metadata=meta_dict if isinstance(meta_dict, dict) else {},
        messages=messages,
    )


def _empty_web_session_payload() -> dict[str, Any]:
    now = datetime.now(timezone.utc).isoformat()
    return {
        "session": {
            "chat_id": "web",
            "key": _session_key("web"),
            "title": "Current conversation",
            "preview": "",
            "created_at": now,
            "updated_at": now,
            "message_count": 0,
        },
        "messages": [],
    }


def _load_session_payload(chat_id: str) -> dict[str, Any] | None:
    loaded = _load_session_file(chat_id)
    if loaded is None:
        return _empty_web_session_payload() if chat_id == "web" else None

    metadata, messages = loaded
    meta_dict = metadata.get("metadata", {})
    summary = _session_summary_from_messages(
        chat_id,
        created_at=str(metadata.get("created_at", "")),
        updated_at=str(metadata.get("updated_at", "")),
        metadata=meta_dict if isinstance(meta_dict, dict) else {},
        messages=messages,
    )
    return {"session": summary, "messages": _serialize_session_messages(messages)}


def _list_web_sessions() -> list[dict[str, Any]]:
    ensure_session_store_dirs()
    sessions: list[dict[str, Any]] = []
    seen_chat_ids: set[str] = set()

    for path in sorted(SESSIONS_DIR.glob("api_*.jsonl")):
        chat_id = path.stem[4:]
        if not _is_web_session_chat_id(chat_id):
            continue
        summary = _load_session_summary(chat_id)
        if summary is None:
            continue
        sessions.append(summary)
        seen_chat_ids.add(chat_id)

    if "web" not in seen_chat_ids:
        now = datetime.now(timezone.utc).isoformat()
        sessions.append(
            {
                "chat_id": "web",
                "key": _session_key("web"),
                "title": "Current conversation",
                "preview": "",
                "created_at": now,
                "updated_at": now,
                "message_count": 0,
            }
        )

    sessions.sort(key=lambda item: str(item.get("updated_at", "")), reverse=True)
    return sessions


def _create_session(chat_id: str, title: str = "") -> dict[str, Any]:
    ensure_session_store_dirs()
    now = datetime.now(timezone.utc).isoformat()
    payload = {
        "_type": "metadata",
        "key": _session_key(chat_id),
        "created_at": now,
        "updated_at": now,
        "metadata": {"title": title.strip()} if title.strip() else {},
        "last_consolidated": 0,
    }
    _session_path(chat_id).write_text(
        json.dumps(payload, ensure_ascii=False) + "\n", encoding="utf-8"
    )
    summary = _load_session_summary(chat_id)
    if summary is None:
        raise RuntimeError("failed to create session")
    return summary


def _write_session_file(
    chat_id: str, metadata: dict[str, Any], messages: list[dict[str, Any]]
) -> dict[str, Any] | None:
    try:
        ensure_session_store_dirs()
        encoded_lines = [json.dumps(metadata, ensure_ascii=False)]
        encoded_lines.extend(json.dumps(message, ensure_ascii=False) for message in messages)
        _session_path(chat_id).write_text("\n".join(encoded_lines) + "\n", encoding="utf-8")
    except OSError:
        return None
    return _load_session_summary(chat_id)


def _rename_session(chat_id: str, title: str) -> dict[str, Any] | None:
    loaded = _load_session_file(chat_id)
    if loaded is None:
        if chat_id != "web":
            return None
        return _create_session(chat_id, title=title)

    metadata, messages = loaded
    meta_dict = metadata.get("metadata", {})
    if not isinstance(meta_dict, dict):
        meta_dict = {}
    if title.strip():
        meta_dict["title"] = title.strip()
    else:
        meta_dict.pop("title", None)

    now = datetime.now(timezone.utc).isoformat()
    normalized_metadata = {
        **metadata,
        "_type": "metadata",
        "key": _session_key(chat_id),
        "created_at": str(metadata.get("created_at", "") or now),
        "updated_at": now,
        "metadata": meta_dict,
        "last_consolidated": metadata.get("last_consolidated", 0),
    }
    return _write_session_file(chat_id, normalized_metadata, messages)


def _delete_session(chat_id: str, delete_cards_for_chat: Callable[[str], int]) -> bool:
    path = _session_path(chat_id)
    deleted_file = False
    if path.exists():
        try:
            path.unlink()
            deleted_file = True
        except OSError:
            return False
    deleted_cards = delete_cards_for_chat(chat_id)
    return deleted_file or deleted_cards > 0 or chat_id == "web"


normalize_session_chat_id = _normalize_session_chat_id
is_web_session_chat_id = _is_web_session_chat_id
list_web_sessions = _list_web_sessions
load_session_payload = _load_session_payload
create_session = _create_session
rename_session = _rename_session
delete_session = _delete_session

__all__ = [
    "SESSIONS_DIR",
    "create_session",
    "delete_session",
    "ensure_session_store_dirs",
    "is_web_session_chat_id",
    "list_web_sessions",
    "load_session_payload",
    "normalize_session_chat_id",
    "rename_session",
]
