from __future__ import annotations

import re
import threading
import time
import uuid
from collections import deque
from dataclasses import dataclass, field
from typing import Any, Deque, Optional


_ANSI_ESCAPE_RE = re.compile(r"\x1B\[[0-?]*[ -/]*[@-~]")


def strip_ansi(value: str) -> str:
    return _ANSI_ESCAPE_RE.sub("", value)


@dataclass
class ConnectionLogEvent:
    seq: int
    timestamp: float
    direction: str
    participants: list[str]
    event: str
    details: dict[str, Any] = field(default_factory=dict)


@dataclass
class ConnectionLogSession:
    id: str
    service: str
    peer: str
    started_at: float
    ended_at: float | None = None
    events: list[ConnectionLogEvent] = field(default_factory=list)
    dropped_events: int = 0
    last_event_at: float = 0.0
    next_seq: int = 1


class ConnectionLogStore:
    def __init__(self, history_limit: int = 64, event_limit: int = 512) -> None:
        self._history_limit = max(1, int(history_limit))
        self._event_limit = max(8, int(event_limit))
        self._lock = threading.Lock()
        self._active: dict[str, ConnectionLogSession] = {}
        self._active_by_service: dict[str, str] = {}
        self._history: Deque[ConnectionLogSession] = deque(maxlen=self._history_limit)
        self._latest_active_id: str | None = None

    def begin(self, service: str, peer: str) -> str:
        now = time.time()
        service_name = service.strip() or "Unknown"
        peer_name = peer.strip() or "unknown"
        with self._lock:
            existing_id = self._active_by_service.get(service_name)
            if existing_id:
                self._end_locked(existing_id, reason="Connection replaced", timestamp=now)
            session_id = f"{service_name.lower().replace(' ', '-')}-{int(now * 1000)}-{uuid.uuid4().hex[:8]}"
            session = ConnectionLogSession(
                id=session_id,
                service=service_name,
                peer=peer_name,
                started_at=now,
                last_event_at=now,
            )
            self._active[session_id] = session
            self._active_by_service[service_name] = session_id
            self._latest_active_id = session_id
            self._append_locked(
                session,
                direction="event",
                participants=[peer_name, service_name],
                event=f"Connection opened from {peer_name}",
                details={"kind": "connection_opened"},
                timestamp=now,
            )
            return session_id

    def append(
        self,
        *,
        event: str,
        direction: str = "event",
        participants: Optional[list[str]] = None,
        details: Optional[dict[str, Any]] = None,
        session_id: Optional[str] = None,
        service: Optional[str] = None,
        timestamp: Optional[float] = None,
    ) -> Optional[str]:
        if not event:
            return None
        when = time.time() if timestamp is None else float(timestamp)
        with self._lock:
            session = self._resolve_session_locked(session_id=session_id, service=service)
            if session is None:
                return None
            self._append_locked(
                session,
                direction=direction,
                participants=participants or [],
                event=event,
                details=details or {},
                timestamp=when,
            )
            self._latest_active_id = session.id
            return session.id

    def append_to_latest(
        self,
        *,
        event: str,
        direction: str = "event",
        participants: Optional[list[str]] = None,
        details: Optional[dict[str, Any]] = None,
        timestamp: Optional[float] = None,
    ) -> Optional[str]:
        return self.append(
            event=event,
            direction=direction,
            participants=participants,
            details=details,
            timestamp=timestamp,
        )

    def end(
        self,
        *,
        session_id: Optional[str] = None,
        service: Optional[str] = None,
        reason: str = "Connection closed",
        timestamp: Optional[float] = None,
    ) -> None:
        when = time.time() if timestamp is None else float(timestamp)
        with self._lock:
            session = self._resolve_session_locked(session_id=session_id, service=service)
            if session is None:
                return
            self._append_locked(
                session,
                direction="event",
                participants=[session.peer, session.service],
                event=reason,
                details={"kind": "connection_closed"},
                timestamp=when,
            )
            self._end_locked(session.id, reason=reason, timestamp=when)

    def close_all_active(self, reason: str = "Connection closed") -> None:
        with self._lock:
            for session_id in list(self._active.keys()):
                self._append_locked(
                    self._active[session_id],
                    direction="event",
                    participants=[self._active[session_id].peer, self._active[session_id].service],
                    event=reason,
                    details={"kind": "connection_closed"},
                    timestamp=time.time(),
                )
                self._end_locked(session_id, reason=reason, timestamp=time.time())

    def summary(self) -> dict[str, Any]:
        with self._lock:
            active = self._active_session_locked()
            if active is None:
                return {
                    "active": False,
                    "historyCount": len(self._history),
                }
            return {
                "active": True,
                "historyCount": len(self._history),
                "sessionId": active.id,
                "service": active.service,
                "peer": active.peer,
                "startedAt": active.started_at,
                "eventCount": len(active.events) + active.dropped_events,
            }

    def active_payload(self) -> dict[str, Any]:
        with self._lock:
            active = self._active_session_locked()
            return {
                "active": active is not None,
                "historyCount": len(self._history),
                "session": self._serialize_session(active) if active is not None else None,
            }

    def active_payload_delta(self, after: Optional[int] = None) -> dict[str, Any]:
        with self._lock:
            active = self._active_session_locked()
            if active is None:
                return {
                    "active": False,
                    "historyCount": len(self._history),
                    "session": None,
                    "cursor": None,
                    "reset": False,
                }

            try:
                after_seq = int(after) if after is not None else None
            except (TypeError, ValueError):
                after_seq = None

            events = list(active.events)
            cursor = active.next_seq - 1
            reset = False

            if after_seq is None:
                selected_events = events
            elif not events:
                selected_events = []
            else:
                first_seq = events[0].seq
                if after_seq < first_seq - 1:
                    selected_events = events
                    reset = True
                else:
                    selected_events = [item for item in events if item.seq > after_seq]

            return {
                "active": True,
                "historyCount": len(self._history),
                "session": self._serialize_session(active, events=selected_events),
                "cursor": cursor,
                "reset": reset,
            }

    def clear_active(self) -> bool:
        with self._lock:
            active = self._active_session_locked()
            if active is None:
                return False
            active.events.clear()
            active.dropped_events = 0
            active.last_event_at = time.time()
            return True

    def _resolve_session_locked(
        self,
        session_id: Optional[str],
        service: Optional[str],
    ) -> Optional[ConnectionLogSession]:
        if session_id:
            return self._active.get(session_id)
        if service:
            mapped_id = self._active_by_service.get(service)
            if mapped_id:
                return self._active.get(mapped_id)
        return self._active_session_locked()

    def _active_session_locked(self) -> Optional[ConnectionLogSession]:
        if not self._active:
            return None
        if self._latest_active_id and self._latest_active_id in self._active:
            return self._active[self._latest_active_id]
        latest = max(self._active.values(), key=lambda item: item.last_event_at)
        self._latest_active_id = latest.id
        return latest

    def _append_locked(
        self,
        session: ConnectionLogSession,
        *,
        direction: str,
        participants: list[str],
        event: str,
        details: dict[str, Any],
        timestamp: float,
    ) -> None:
        clean_event = strip_ansi(event.strip()) if isinstance(event, str) else str(event)
        if len(session.events) >= self._event_limit:
            session.events.pop(0)
            session.dropped_events += 1
        session.events.append(
            ConnectionLogEvent(
                seq=session.next_seq,
                timestamp=timestamp,
                direction=(direction or "event").strip() or "event",
                participants=[part for part in participants if part],
                event=clean_event,
                details=details,
            )
        )
        session.next_seq += 1
        session.last_event_at = timestamp

    def _end_locked(self, session_id: str, reason: str, timestamp: float) -> None:
        session = self._active.pop(session_id, None)
        if session is None:
            return
        session.ended_at = timestamp
        self._active_by_service.pop(session.service, None)
        self._history.appendleft(session)
        if self._latest_active_id == session_id:
            self._latest_active_id = None

    def _serialize_session(
        self,
        session: ConnectionLogSession,
        events: Optional[list[ConnectionLogEvent]] = None,
    ) -> dict[str, Any]:
        serialized_events = session.events if events is None else events
        return {
            "id": session.id,
            "service": session.service,
            "peer": session.peer,
            "startedAt": session.started_at,
            "endedAt": session.ended_at,
            "eventCount": len(session.events) + session.dropped_events,
            "droppedEvents": session.dropped_events,
            "cursor": session.next_seq - 1,
            "events": [
                {
                    "seq": item.seq,
                    "timestamp": item.timestamp,
                    "direction": item.direction,
                    "participants": item.participants,
                    "event": item.event,
                    "details": item.details,
                }
                for item in serialized_events
            ],
        }
