import hashlib
import secrets
import threading
import time
from dataclasses import dataclass
from typing import Any

try:
    import psycopg
    from psycopg.rows import dict_row
except Exception:  # pragma: no cover
    psycopg = None
    dict_row = None


def _now_ms() -> int:
    return int(time.time() * 1000)


def hash_value(value: str) -> str:
    return hashlib.sha256(value.encode("utf-8")).hexdigest()


def new_session_id() -> str:
    return secrets.token_urlsafe(32)


@dataclass
class SessionRecord:
    session_id: str
    session_hash: str
    created_at_ms: int
    last_seen_at_ms: int
    first_ip_hash: str
    last_ip_hash: str
    user_agent_hash: str
    risk_score: int = 0
    blocked_until_ms: int = 0
    is_new: bool = False


class BaseSessionStore:
    def get_or_create(self, session_id: str | None, *, client_ip_hash: str, user_agent_hash: str) -> SessionRecord:
        raise NotImplementedError


class InMemorySessionStore(BaseSessionStore):
    def __init__(self) -> None:
        self.sessions: dict[str, SessionRecord] = {}
        self.lock = threading.Lock()

    def get_or_create(self, session_id: str | None, *, client_ip_hash: str, user_agent_hash: str) -> SessionRecord:
        now = _now_ms()
        with self.lock:
            if session_id:
                session_hash = hash_value(session_id)
                record = self.sessions.get(session_hash)
                if record is not None:
                    record.last_seen_at_ms = now
                    record.last_ip_hash = client_ip_hash
                    record.user_agent_hash = user_agent_hash
                    record.is_new = False
                    return record
            next_session_id = new_session_id()
            next_hash = hash_value(next_session_id)
            record = SessionRecord(
                session_id=next_session_id,
                session_hash=next_hash,
                created_at_ms=now,
                last_seen_at_ms=now,
                first_ip_hash=client_ip_hash,
                last_ip_hash=client_ip_hash,
                user_agent_hash=user_agent_hash,
                is_new=True,
            )
            self.sessions[next_hash] = record
            return record


class PostgresSessionStore(BaseSessionStore):
    def __init__(self, database_url: str) -> None:
        if psycopg is None or dict_row is None:
            raise RuntimeError("psycopg 未安装，无法使用 PostgreSQL session 存储")
        self.database_url = database_url
        self._init_lock = threading.Lock()
        self._initialized = False

    def _connect(self):
        return psycopg.connect(self.database_url, autocommit=True, row_factory=dict_row)

    def _ensure_initialized(self) -> None:
        if self._initialized:
            return
        with self._init_lock:
            if self._initialized:
                return
            with self._connect() as conn:
                with conn.cursor() as cur:
                    cur.execute(
                        """
                        CREATE TABLE IF NOT EXISTS anonymous_sessions (
                            session_hash TEXT PRIMARY KEY,
                            created_at_ms BIGINT NOT NULL,
                            last_seen_at_ms BIGINT NOT NULL,
                            first_ip_hash TEXT NOT NULL,
                            last_ip_hash TEXT NOT NULL,
                            user_agent_hash TEXT NOT NULL,
                            risk_score INTEGER NOT NULL DEFAULT 0,
                            blocked_until_ms BIGINT NOT NULL DEFAULT 0,
                            metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb
                        )
                        """
                    )
                    cur.execute(
                        "CREATE INDEX IF NOT EXISTS anonymous_sessions_last_seen_idx ON anonymous_sessions(last_seen_at_ms)"
                    )
            self._initialized = True

    def get_or_create(self, session_id: str | None, *, client_ip_hash: str, user_agent_hash: str) -> SessionRecord:
        self._ensure_initialized()
        now = _now_ms()
        if session_id:
            session_hash = hash_value(session_id)
            with self._connect() as conn:
                with conn.cursor() as cur:
                    cur.execute(
                        """
                        UPDATE anonymous_sessions
                        SET last_seen_at_ms = %s,
                            last_ip_hash = %s,
                            user_agent_hash = %s
                        WHERE session_hash = %s
                        RETURNING session_hash, created_at_ms, last_seen_at_ms, first_ip_hash, last_ip_hash, user_agent_hash, risk_score, blocked_until_ms
                        """,
                        (now, client_ip_hash, user_agent_hash, session_hash),
                    )
                    row = cur.fetchone()
                    if row is not None:
                        return SessionRecord(
                            session_id=session_id,
                            session_hash=row["session_hash"],
                            created_at_ms=int(row["created_at_ms"]),
                            last_seen_at_ms=int(row["last_seen_at_ms"]),
                            first_ip_hash=row["first_ip_hash"],
                            last_ip_hash=row["last_ip_hash"],
                            user_agent_hash=row["user_agent_hash"],
                            risk_score=int(row["risk_score"] or 0),
                            blocked_until_ms=int(row["blocked_until_ms"] or 0),
                            is_new=False,
                        )
        next_session_id = new_session_id()
        next_hash = hash_value(next_session_id)
        with self._connect() as conn:
            with conn.cursor() as cur:
                cur.execute(
                    """
                    INSERT INTO anonymous_sessions (
                        session_hash, created_at_ms, last_seen_at_ms, first_ip_hash, last_ip_hash, user_agent_hash
                    )
                    VALUES (%s, %s, %s, %s, %s, %s)
                    """,
                    (next_hash, now, now, client_ip_hash, client_ip_hash, user_agent_hash),
                )
        return SessionRecord(
            session_id=next_session_id,
            session_hash=next_hash,
            created_at_ms=now,
            last_seen_at_ms=now,
            first_ip_hash=client_ip_hash,
            last_ip_hash=client_ip_hash,
            user_agent_hash=user_agent_hash,
            is_new=True,
        )


_session_store: BaseSessionStore | None = None


def get_session_store(database_url: str | None = None) -> BaseSessionStore:
    global _session_store
    if _session_store is not None:
        return _session_store
    if database_url:
        _session_store = PostgresSessionStore(database_url)
    else:
        _session_store = InMemorySessionStore()
    return _session_store


def reset_session_store() -> None:
    global _session_store
    _session_store = None
