from __future__ import annotations

import json
import os
import sqlite3
from datetime import UTC, datetime, timedelta
from typing import Any


class PendingActionStore:
    def __init__(self, db_path: str) -> None:
        self.db_path = db_path
        self._ensure_db()

    def put(self, token: str, payload: dict[str, Any], ttl_minutes: int = 120) -> None:
        expires_at = datetime.now(UTC) + timedelta(minutes=ttl_minutes)
        with self._connect() as conn:
            conn.execute(
                """
                INSERT INTO pending_actions (token, payload_json, expires_at, created_at)
                VALUES (?, ?, ?, ?)
                ON CONFLICT(token) DO UPDATE SET
                    payload_json=excluded.payload_json,
                    expires_at=excluded.expires_at
                """,
                (token, json.dumps(payload), expires_at.isoformat(), datetime.now(UTC).isoformat()),
            )

    def pop(self, token: str) -> dict[str, Any] | None:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT payload_json, expires_at FROM pending_actions WHERE token = ?",
                (token,),
            ).fetchone()
            conn.execute("DELETE FROM pending_actions WHERE token = ?", (token,))

        if not row:
            return None

        expires_at = datetime.fromisoformat(row[1])
        if expires_at < datetime.now(UTC):
            return None

        return json.loads(row[0])

    def get(self, token: str) -> dict[str, Any] | None:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT payload_json, expires_at FROM pending_actions WHERE token = ?",
                (token,),
            ).fetchone()

        if not row:
            return None

        expires_at = datetime.fromisoformat(row[1])
        if expires_at < datetime.now(UTC):
            self.delete(token)
            return None

        return json.loads(row[0])

    def delete(self, token: str) -> None:
        with self._connect() as conn:
            conn.execute("DELETE FROM pending_actions WHERE token = ?", (token,))

    def cleanup(self) -> None:
        with self._connect() as conn:
            conn.execute(
                "DELETE FROM pending_actions WHERE expires_at < ?",
                (datetime.now(UTC).isoformat(),),
            )

    def _ensure_db(self) -> None:
        os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
        with self._connect() as conn:
            conn.execute(
                """
                CREATE TABLE IF NOT EXISTS pending_actions (
                    token TEXT PRIMARY KEY,
                    payload_json TEXT NOT NULL,
                    expires_at TEXT NOT NULL,
                    created_at TEXT NOT NULL
                )
                """
            )

    def _connect(self) -> sqlite3.Connection:
        conn = sqlite3.connect(self.db_path)
        conn.execute("PRAGMA journal_mode=WAL")
        return conn


class RateLimitStore:
    """Rate limiter persistent berbasis SQLite (shared antar Passenger workers).
    Sliding-window: maksimal N request dalam M detik per chat_id.
    """

    def __init__(
        self,
        db_path: str,
        window_seconds: int = 10,
        max_requests: int = 30,
    ) -> None:
        self._db_path = db_path
        self._window_seconds = window_seconds
        self._max_requests = max_requests
        self._ensure_db()

    def is_rate_limited(self, chat_id: int) -> bool:
        """Return True jika chat_id melebihi limit dalam window."""
        import time as _time

        now = _time.time()
        cutoff = now - self._window_seconds

        try:
            with self._connect() as conn:
                row = conn.execute(
                    "SELECT timestamps_json FROM rate_limits WHERE chat_id = ?",
                    (chat_id,),
                ).fetchone()

            timestamps: list[float] = json.loads(row[0]) if row else []
            # Filter timestamp yang masih dalam window
            timestamps = [t for t in timestamps if t > cutoff]
            timestamps.append(now)

            # Simpan kembali
            with self._connect() as conn:
                conn.execute(
                    """
                    INSERT INTO rate_limits (chat_id, timestamps_json, updated_at)
                    VALUES (?, ?, ?)
                    ON CONFLICT(chat_id) DO UPDATE SET
                        timestamps_json=excluded.timestamps_json,
                        updated_at=excluded.updated_at
                    """,
                    (chat_id, json.dumps(timestamps), now),
                )

            return len(timestamps) > self._max_requests
        except Exception:
            # Jika SQLite error, jangan blokir request (fail open)
            return False

    def _ensure_db(self) -> None:
        import os as _os

        _os.makedirs(_os.path.dirname(self._db_path), exist_ok=True)
        with self._connect() as conn:
            conn.execute(
                """
                CREATE TABLE IF NOT EXISTS rate_limits (
                    chat_id INTEGER PRIMARY KEY,
                    timestamps_json TEXT NOT NULL,
                    updated_at REAL NOT NULL
                )
                """
            )

    def _connect(self) -> sqlite3.Connection:
        conn = sqlite3.connect(self._db_path)
        conn.execute("PRAGMA journal_mode=WAL")
        return conn


class GposTokenStore:
    """Penyimpanan token GPOS di SQLite (shared antar Passenger workers)."""

    def __init__(self, db_path: str) -> None:
        self._db_path = db_path
        self._ensure_db()

    def read_token(self) -> tuple[str | None, float]:
        """Baca token yang masih valid. Return (token, expires_at_timestamp) atau (None, 0.0)."""
        import time as _time

        try:
            with self._connect() as conn:
                row = conn.execute(
                    "SELECT token, expires_at FROM gpos_token_cache WHERE expires_at > ?",
                    (_time.time(),),
                ).fetchone()
            if row:
                return row[0], float(row[1])
        except Exception:
            pass
        return None, 0.0

    def write_token(self, token: str, ttl_seconds: int = 43200) -> None:
        """Simpan token ke SQLite (12 jam default TTL)."""
        import time as _time

        expires_at = _time.time() + ttl_seconds
        try:
            with self._connect() as conn:
                conn.execute(
                    """
                    INSERT INTO gpos_token_cache (token, expires_at, updated_at)
                    VALUES (?, ?, ?)
                    ON CONFLICT(token) DO UPDATE SET
                        expires_at=excluded.expires_at,
                        updated_at=excluded.updated_at
                    """,
                    (token, expires_at, _time.time()),
                )
        except Exception:
            pass

    def clear_expired(self) -> None:
        """Hapus token yang sudah kadaluarsa."""
        import time as _time

        try:
            with self._connect() as conn:
                conn.execute(
                    "DELETE FROM gpos_token_cache WHERE expires_at < ?",
                    (_time.time(),),
                )
        except Exception:
            pass

    def _ensure_db(self) -> None:
        import os as _os

        _os.makedirs(_os.path.dirname(self._db_path), exist_ok=True)
        with self._connect() as conn:
            conn.execute(
                """
                CREATE TABLE IF NOT EXISTS gpos_token_cache (
                    token TEXT PRIMARY KEY,
                    expires_at REAL NOT NULL,
                    updated_at REAL NOT NULL
                )
                """
            )

    def _connect(self) -> sqlite3.Connection:
        conn = sqlite3.connect(self._db_path)
        conn.execute("PRAGMA journal_mode=WAL")
        return conn
