#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import annotations

import threading
from datetime import timedelta
from typing import List, Optional

import db_pool
from db.utils import get_cst_now, get_cst_now_str


class BlacklistManager:
    """黑名单管理器"""

    def __init__(self) -> None:
        self._schema_ready = False
        self._schema_lock = threading.Lock()

    def is_ip_banned(self, ip: str) -> bool:
        """检查IP是否被封禁"""
        ip_text = str(ip or "").strip()[:64]
        if not ip_text:
            return False
        now_str = get_cst_now_str()

        with db_pool.get_db() as conn:
            cursor = conn.cursor()
            cursor.execute(
                """
                SELECT 1
                FROM ip_blacklist
                WHERE ip = ?
                  AND is_active = 1
                  AND (expires_at IS NULL OR expires_at > ?)
                LIMIT 1
                """,
                (ip_text, now_str),
            )
            return cursor.fetchone() is not None

    def is_user_banned(self, user_id: int) -> bool:
        """检查用户是否被封禁"""
        if user_id is None:
            return False
        self._ensure_schema()
        user_id_int = int(user_id)
        now_str = get_cst_now_str()

        with db_pool.get_db() as conn:
            cursor = conn.cursor()
            cursor.execute(
                """
                SELECT 1
                FROM user_blacklist
                WHERE user_id = ?
                  AND is_active = 1
                  AND (expires_at IS NULL OR expires_at > ?)
                LIMIT 1
                """,
                (user_id_int, now_str),
            )
            return cursor.fetchone() is not None

    def ban_ip(self, ip: str, reason: str, duration_hours: int = 24, permanent: bool = False):
        """封禁IP"""
        ip_text = str(ip or "").strip()[:64]
        if not ip_text:
            return False
        reason_text = str(reason or "").strip()[:512]
        now_str = get_cst_now_str()

        expires_at: Optional[str]
        if permanent:
            expires_at = None
        else:
            hours = max(1, int(duration_hours))
            expires_at = (get_cst_now() + timedelta(hours=hours)).strftime("%Y-%m-%d %H:%M:%S")

        with db_pool.get_db() as conn:
            cursor = conn.cursor()
            cursor.execute(
                """
                INSERT INTO ip_blacklist (ip, reason, is_active, added_at, expires_at)
                VALUES (?, ?, 1, ?, ?)
                ON CONFLICT(ip) DO UPDATE SET
                    reason = excluded.reason,
                    is_active = 1,
                    added_at = excluded.added_at,
                    expires_at = excluded.expires_at
                """,
                (ip_text, reason_text, now_str, expires_at),
            )
            conn.commit()
            return True

    def ban_user(self, user_id: int, reason: str):
        """封禁用户"""
        return self._ban_user_internal(user_id, reason=reason, duration_hours=24, permanent=False)

    def unban_ip(self, ip: str):
        """解除IP封禁"""
        ip_text = str(ip or "").strip()[:64]
        if not ip_text:
            return False
        with db_pool.get_db() as conn:
            cursor = conn.cursor()
            cursor.execute("UPDATE ip_blacklist SET is_active = 0 WHERE ip = ?", (ip_text,))
            conn.commit()
            return cursor.rowcount > 0

    def unban_user(self, user_id: int):
        """解除用户封禁"""
        if user_id is None:
            return False
        self._ensure_schema()
        user_id_int = int(user_id)
        with db_pool.get_db() as conn:
            cursor = conn.cursor()
            cursor.execute("UPDATE user_blacklist SET is_active = 0 WHERE user_id = ?", (user_id_int,))
            conn.commit()
            return cursor.rowcount > 0

    def get_banned_ips(self) -> List[dict]:
        """获取所有被封禁的IP"""
        now_str = get_cst_now_str()
        with db_pool.get_db() as conn:
            cursor = conn.cursor()
            cursor.execute(
                """
                SELECT ip, reason, is_active, added_at, expires_at
                FROM ip_blacklist
                WHERE is_active = 1
                  AND (expires_at IS NULL OR expires_at > ?)
                ORDER BY added_at DESC
                """,
                (now_str,),
            )
            return [dict(row) for row in cursor.fetchall()]

    def get_banned_users(self) -> List[dict]:
        """获取所有被封禁的用户"""
        self._ensure_schema()
        now_str = get_cst_now_str()
        with db_pool.get_db() as conn:
            cursor = conn.cursor()
            cursor.execute(
                """
                SELECT user_id, reason, is_active, added_at, expires_at
                FROM user_blacklist
                WHERE is_active = 1
                  AND (expires_at IS NULL OR expires_at > ?)
                ORDER BY added_at DESC
                """,
                (now_str,),
            )
            return [dict(row) for row in cursor.fetchall()]

    def cleanup_expired(self):
        """清理过期的封禁记录"""
        now_str = get_cst_now_str()
        with db_pool.get_db() as conn:
            cursor = conn.cursor()
            cursor.execute(
                """
                UPDATE ip_blacklist
                SET is_active = 0
                WHERE is_active = 1
                  AND expires_at IS NOT NULL
                  AND expires_at <= ?
                """,
                (now_str,),
            )
            conn.commit()

        self._ensure_schema()
        with db_pool.get_db() as conn:
            cursor = conn.cursor()
            cursor.execute(
                """
                UPDATE user_blacklist
                SET is_active = 0
                WHERE is_active = 1
                  AND expires_at IS NOT NULL
                  AND expires_at <= ?
                """,
                (now_str,),
            )
            conn.commit()

    # ==================== Internal ====================

    def _ensure_schema(self) -> None:
        if self._schema_ready:
            return
        with self._schema_lock:
            if self._schema_ready:
                return
            with db_pool.get_db() as conn:
                cursor = conn.cursor()
                cursor.execute(
                    """
                    CREATE TABLE IF NOT EXISTS user_blacklist (
                        user_id INTEGER PRIMARY KEY,
                        reason TEXT,
                        is_active INTEGER DEFAULT 1,
                        added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                        expires_at TIMESTAMP
                    )
                    """
                )
                cursor.execute("CREATE INDEX IF NOT EXISTS idx_user_blacklist_active ON user_blacklist(is_active)")
                cursor.execute("CREATE INDEX IF NOT EXISTS idx_user_blacklist_expires ON user_blacklist(expires_at)")
                conn.commit()
            self._schema_ready = True

    def _ban_user_internal(
        self,
        user_id: int,
        *,
        reason: str,
        duration_hours: int = 24,
        permanent: bool = False,
    ) -> bool:
        if user_id is None:
            return False
        self._ensure_schema()
        user_id_int = int(user_id)
        reason_text = str(reason or "").strip()[:512]
        now_str = get_cst_now_str()

        expires_at: Optional[str]
        if permanent:
            expires_at = None
        else:
            hours = max(1, int(duration_hours))
            expires_at = (get_cst_now() + timedelta(hours=hours)).strftime("%Y-%m-%d %H:%M:%S")

        with db_pool.get_db() as conn:
            cursor = conn.cursor()
            cursor.execute(
                """
                INSERT INTO user_blacklist (user_id, reason, is_active, added_at, expires_at)
                VALUES (?, ?, 1, ?, ?)
                ON CONFLICT(user_id) DO UPDATE SET
                    reason = excluded.reason,
                    is_active = 1,
                    added_at = excluded.added_at,
                    expires_at = excluded.expires_at
                """,
                (user_id_int, reason_text, now_str, expires_at),
            )
            conn.commit()
            return True

