import asyncio
import logging
from aiogram import Bot, Dispatcher, F, Router, types, BaseMiddleware
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, ReplyKeyboardMarkup, KeyboardButton, FSInputFile
from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application
import aiomysql
from dotenv import load_dotenv
import os
from aiohttp import web, web_exceptions
from typing import Callable, Dict, Any, Awaitable

load_dotenv()
BOT_TOKEN = os.getenv('BOT_TOKEN')
DB_HOST = os.getenv('DB_HOST', 'localhost')
DB_USER = os.getenv('DB_USER', 'root')
DB_PASS = os.getenv('DB_PASS')
DB_NAME = os.getenv('DB_NAME', 'netwell_bot')
WEBHOOK_SECRET = os.getenv('WEBHOOK_SECRET', 'secret')
WEBHOOK_PATH = os.getenv('WEBHOOK_PATH', '/webhook')
ADMIN_ID = int(os.getenv('ADMIN_ID', 0))  # Твой Telegram ID для админ-команд

logging.basicConfig(level=logging.INFO)
bot = Bot(token=BOT_TOKEN)
storage = MemoryStorage()
dp = Dispatcher(storage=storage)
router = Router()
dp.include_router(router)

# Middleware для верификации secret_token в webhook (защита от фейковых запросов)
class SecretTokenMiddleware(BaseMiddleware):
    def __init__(self, secret_token: str):
        self.secret_token = secret_token

    async def __call__(
        self,
        handler: Callable[[Any, Dict[str, Any]], Awaitable[Any]],
        event: Any,
        data: Dict[str, Any]
    ) -> Any:
        if hasattr(event, 'request') and event.request.headers.get('X-Telegram-Bot-Api-Secret-Token') != self.secret_token:
            logging.warning("Invalid secret token")
            raise web_exceptions.HTTPForbidden("Invalid secret token")
        return await handler(event, data)

dp.update.outer_middleware(SecretTokenMiddleware(WEBHOOK_SECRET))

# Состояния для анкеты и поиска
class ProfileForm(StatesGroup):
    name = State()
    city = State()
    phone = State()
    corporate = State()
    email = State()
    about = State()

class SearchState(StatesGroup):
    search = State()

# Глобальный пул DB
pool = None

async def init_db():
    global pool
    try:
        pool = await aiomysql.create_pool(host=DB_HOST, user=DB_USER, password=DB_PASS, db=DB_NAME, autocommit=True)
        logging.info("DB подключена")
    except Exception as e:
        logging.error(f"Ошибка DB: {e}")

async def save_user(user_id: int, **kwargs):
    if not pool:
        return False
    try:
        async with pool.acquire() as conn:
            async with conn.cursor() as cur:
                # Проверка блокировки
                await cur.execute("SELECT blocked FROM users WHERE user_id = %s", (user_id,))
                result = await cur.fetchone()
                if result and result[0]:
                    return False
                if kwargs:
                    # Обновление только указанных полей (prepared statement)
                    updates = ', '.join([f"{k} = %s" for k in kwargs.keys()])
                    await cur.execute(f"UPDATE users SET {updates} WHERE user_id = %s", tuple(kwargs.values()) + (user_id,))
                    if cur.rowcount == 0:
                        # INSERT если нет записи
                        fields = ', '.join(['user_id'] + list(kwargs.keys()))
                        placeholders = ', '.join(['%s'] * (len(kwargs) + 1))
                        values = (user_id,) + tuple(kwargs.values())
                        await cur.execute(f"INSERT INTO users ({fields}) VALUES ({placeholders})", values)
        return True
    except Exception as e:
        logging.error(f"Ошибка сохранения: {e}")
        return False

async def get_user(user_id: int):
    if not pool:
        return None
    try:
        async with pool.acquire() as conn:
            async with conn.cursor(aiomysql.DictCursor) as cur:
                await cur.execute("SELECT * FROM users WHERE user_id = %s AND blocked = FALSE", (user_id,))
                return await cur.fetchone()
    except Exception as e:
        logging.error(f"Ошибка получения пользователя: {e}")
        return None

async def search_user(name_query: str):
    if not pool:
        return []
    try:
        async with pool.acquire() as conn:
            async with conn.cursor(aiomysql.DictCursor) as cur:
                await cur.execute("SELECT name, phone, email, corporate, city as office, about FROM users WHERE name LIKE %s AND blocked = FALSE LIMIT 10", (f"%{name_query}%",))
                return await cur.fetchall()
    except Exception as e:
        logging.error(f"Ошибка поиска: {e}")
        return []

async def get_events(event_type: str):
    if not pool:
        return []
    try:
        async with pool.acquire() as conn:
            async with conn.cursor(aiomysql.DictCursor) as cur:
                await cur.execute("SELECT date, description FROM events WHERE type = %s", (event_type,))
                return await cur.fetchall()
    except Exception as e:
        logging.error(f"Ошибка событий: {e}")
        return []

# Рассылка по городу
async def broadcast_by_city(city: str, message: str):
    if not pool:
        return
    try:
        async with pool.acquire() as conn:
            async with conn.cursor() as cur:
                await cur.execute("SELECT user_id FROM users WHERE city = %s AND blocked = FALSE", (city,))
                users = [row[0] async for row in cur]
        for uid in users:
            try:
                await bot.send_message(uid, message)
            except Exception:
                pass  # Игнор удалённых
    except Exception as e:
        logging.error(f"Ошибка рассылки: {e}")

# Рассылка всем (для ВСЕ)
async def broadcast_all(message: str):
    if not pool:
        return
    try:
        async with pool.acquire() as conn:
            async with conn.cursor() as cur:
                await cur.execute("SELECT user_id FROM users WHERE blocked = FALSE")
                users = [row[0] async for row in cur]
        for uid in users:
            try:
                await bot.send_message(uid, message)
            except Exception:
                pass
    except Exception as e:
        logging.error(f"Ошибка рассылки всем: {e}")

# /start
@router.message(Command("start"))
async def start_handler(message: types.Message, state: FSMContext):
    user = await get_user(message.from_user.id)
    if not user:
        await message.answer("Привет, я корпоративный бот Нетвелл и буду твоим помощником. Для доступа в меню и подписки, нажми кнопку СТАРТ.")
        kb = ReplyKeyboardMarkup(keyboard=[[KeyboardButton(text="СТАРТ")]], resize_keyboard=True)
        await message.answer("Нажми СТАРТ для анкеты.", reply_markup=kb)
    else:
        await message.answer("Отлично, теперь ты часть Нетвелл! Нажми кнопку МЕНЮ, чтобы получить информацию о компании, коллегах или изменить свои данные")
        kb = ReplyKeyboardMarkup(keyboard=[[KeyboardButton(text="МЕНЮ")]], resize_keyboard=True)
        await message.answer("МЕНЮ", reply_markup=kb)

# Анкета (СТАРТ)
@router.message(F.text == "СТАРТ")
async def anketa_start(message: types.Message, state: FSMContext):
    await state.set_state(ProfileForm.name)
    await message.answer("Заполни короткую анкету, чтобы мы могли познакомиться. Расскажи кратко о себе, прикрепи любимый трек или мем. Важно: информация будет доступна коллегам.\nИмя и Фамилия:")

@router.message(ProfileForm.name)
async def process_name(message: types.Message, state: FSMContext):
    await state.update_data(name=message.text.strip())
    kb = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text="Москва", callback_data="city_msk")],
        [InlineKeyboardButton(text="Санкт-Петербург", callback_data="city_spb")]
    ])
    await state.set_state(ProfileForm.city)
    await message.answer("Выберите Город:", reply_markup=kb)

@router.callback_query(F.data.startswith("city_"))
async def process_city(callback: types.CallbackQuery, state: FSMContext):
    city = "Москва" if callback.data == "city_msk" else "Санкт-Петербург"
    await state.update_data(city=city)
    await state.set_state(ProfileForm.phone)
    await callback.message.answer("Мобильный телефон:")
    await callback.answer()

@router.message(ProfileForm.phone)
async def process_phone(message: types.Message, state: FSMContext):
    phone = message.text.strip()
    if not phone.isdigit():
        await message.answer("Введите только цифры для телефона.")
        return
    await state.update_data(phone=phone)
    await state.set_state(ProfileForm.corporate)
    await message.answer("Корпоративный номер:")

@router.message(ProfileForm.corporate)
async def process_corporate(message: types.Message, state: FSMContext):
    corp = message.text.strip()
    if not corp.isdigit():
        await message.answer("Введите только цифры.")
        return
    await state.update_data(corporate=corp)
    await state.set_state(ProfileForm.email)
    await message.answer("Почта:")

@router.message(ProfileForm.email)
async def process_email(message: types.Message, state: FSMContext):
    email = message.text.strip()
    if '@' not in email:
        await message.answer("Некорректная почта (должен быть @).")
        return
    await state.update_data(email=email)
    await state.set_state(ProfileForm.about)
    await message.answer("Кратко о себе:")

@router.message(ProfileForm.about)
async def process_about(message: types.Message, state: FSMContext):
    data = await state.get_data()
    data['about'] = message.text.strip()
    if await save_user(message.from_user.id, **data):
        await state.clear()
        await message.answer("Отлично, теперь ты часть Нетвелл! Нажми кнопку МЕНЮ, чтобы получить информацию о компании, коллегах или изменить свои данные")
        kb = ReplyKeyboardMarkup(keyboard=[[KeyboardButton(text="МЕНЮ")]], resize_keyboard=True)
        await message.answer("МЕНЮ", reply_markup=kb)
    else:
        await message.answer("Ошибка сохранения. Попробуйте позже.")

# Меню
@router.message(F.text == "МЕНЮ", Command("menu"))
async def menu_handler(message: types.Message):
    kb = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text="Изменить анкету", callback_data="profile")],
        [InlineKeyboardButton(text="О компании", callback_data="company")],
        [InlineKeyboardButton(text="Справочник", callback_data="contacts")],
        [InlineKeyboardButton(text="Поиск контактов", callback_data="search")],
        [InlineKeyboardButton(text="Календарь мероприятий", callback_data="event")]
    ])
    await message.answer("Меню:", reply_markup=kb)

@router.callback_query(F.data == "back")
async def back_to_menu(callback: types.CallbackQuery):
    kb = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text="Изменить анкету", callback_data="profile")],
        [InlineKeyboardButton(text="О компании", callback_data="company")],
        [InlineKeyboardButton(text="Справочник", callback_data="contacts")],
        [InlineKeyboardButton(text="Поиск контактов", callback_data="search")],
        [InlineKeyboardButton(text="Календарь мероприятий", callback_data="event")]
    ])
    await callback.message.answer("Меню:", reply_markup=kb)
    await callback.answer()

# Изменить анкету
@router.callback_query(F.data == "profile")
async def profile_menu(callback: types.CallbackQuery):
    kb = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text="Имя и Фамилия", callback_data="change_name")],
        [InlineKeyboardButton(text="Город", callback_data="change_city")],
        [InlineKeyboardButton(text="Мобильный телефон", callback_data="change_phone")],
        [InlineKeyboardButton(text="Корпоративный номер", callback_data="change_corporate")],
        [InlineKeyboardButton(text="Почта", callback_data="change_email")],
        [InlineKeyboardButton(text="Кратко о себе", callback_data="change_about")],
        [InlineKeyboardButton(text="Назад в меню", callback_data="back")]
    ])
    await callback.message.answer("Выбери, что бы ты хотел изменить", reply_markup=kb)
    await callback.answer()

# Изменения анкеты (полные handlers)
@router.callback_query(F.data == "change_name")
async def change_name_start(callback: types.CallbackQuery, state: FSMContext):
    await state.set_state(ProfileForm.name)
    await callback.message.answer("Введите новое Имя и Фамилию:")
    await callback.answer()

@router.message(ProfileForm.name)
async def change_name_process(message: types.Message, state: FSMContext):
    if await save_user(message.from_user.id, name=message.text.strip()):
        await message.answer("Спасибо, ваши данные изменены!")
    else:
        await message.answer("Ошибка изменения.")
    await state.clear()
    await menu_handler(message)

@router.callback_query(F.data == "change_city")
async def change_city_start(callback: types.CallbackQuery, state: FSMContext):
    kb = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text="Москва", callback_data="update_city_msk")],
        [InlineKeyboardButton(text="Санкт-Петербург", callback_data="update_city_spb")],
        [InlineKeyboardButton(text="Назад", callback_data="back")]
    ])
    await callback.message.answer("Выберите новый Город:", reply_markup=kb)
    await callback.answer()

@router.callback_query(F.data.startswith("update_city_"))
async def change_city_process(callback: types.CallbackQuery, state: FSMContext):
    city = "Москва" if callback.data == "update_city_msk" else "Санкт-Петербург"
    if await save_user(callback.from_user.id, city=city):
        await callback.message.answer("Спасибо, ваши данные изменены!")
    else:
        await callback.message.answer("Ошибка изменения.")
    await back_to_menu(callback)

@router.callback_query(F.data == "change_phone")
async def change_phone_start(callback: types.CallbackQuery, state: FSMContext):
    await state.set_state(ProfileForm.phone)
    await callback.message.answer("Введите новый Мобильный телефон:")
    await callback.answer()

@router.message(ProfileForm.phone)
async def change_phone_process(message: types.Message, state: FSMContext):
    phone = message.text.strip()
    if not phone.isdigit():
        await message.answer("Введите только цифры.")
        return
    if await save_user(message.from_user.id, phone=phone):
        await message.answer("Спасибо, ваши данные изменены!")
    else:
        await message.answer("Ошибка изменения.")
    await state.clear()
    await menu_handler(message)

@router.callback_query(F.data == "change_corporate")
async def change_corporate_start(callback: types.CallbackQuery, state: FSMContext):
    await state.set_state(ProfileForm.corporate)
    await callback.message.answer("Введите новый Корпоративный номер:")
    await callback.answer()

@router.message(ProfileForm.corporate)
async def change_corporate_process(message: types.Message, state: FSMContext):
    corp = message.text.strip()
    if not corp.isdigit():
        await message.answer("Введите только цифры.")
        return
    if await save_user(message.from_user.id, corporate=corp):
        await message.answer("Спасибо, ваши данные изменены!")
    else:
        await message.answer("Ошибка изменения.")
    await state.clear()
    await menu_handler(message)

@router.callback_query(F.data == "change_email")
async def change_email_start(callback: types.CallbackQuery, state: FSMContext):
    await state.set_state(ProfileForm.email)
    await callback.message.answer("Введите новую Почту:")
    await callback.answer()

@router.message(ProfileForm.email)
async def change_email_process(message: types.Message, state: FSMContext):
    email = message.text.strip()
    if '@' not in email:
        await message.answer("Некорректная почта.")
        return
    if await save_user(message.from_user.id, email=email):
        await message.answer("Спасибо, ваши данные изменены!")
    else:
        await message.answer("Ошибка изменения.")
    await state.clear()
    await menu_handler(message)

@router.callback_query(F.data == "change_about")
async def change_about_start(callback: types.CallbackQuery, state: FSMContext):
    await state.set_state(ProfileForm.about)
    await callback.message.answer("Введите новое описание о себе:")
    await callback.answer()

@router.message(ProfileForm.about)
async def change_about_process(message: types.Message, state: FSMContext):
    if await save_user(message.from_user.id, about=message.text.strip()):
        await message.answer("Спасибо, ваши данные изменены!")
    else:
        await message.answer("Ошибка изменения.")
    await state.clear()
    await menu_handler(message)

# О компании (отправка файлов из /var/www/netwell/files/)
@router.callback_query(F.data == "company")
async def company_menu(callback: types.CallbackQuery):
    kb = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text="Презентация корпоративная", callback_data="presentation")],
        [InlineKeyboardButton(text="Презентация внешняя", callback_data="products")],
        [InlineKeyboardButton(text="Гайдлайн", callback_data="brandbook")],
        [InlineKeyboardButton(text="Логотипы", callback_data="logo")],
        [InlineKeyboardButton(text="Назад в меню", callback_data="back")]
    ])
    await callback.message.answer("Выберите нужный файл", reply_markup=kb)
    await callback.answer()

@router.callback_query(F.data == "presentation")
async def send_presentation(callback: types.CallbackQuery):
    try:
        await bot.send_document(callback.from_user.id, FSInputFile('/var/www/netwell/files/presentation.pdf'))
    except Exception as e:
        await callback.message.answer(f"Ошибка отправки файла: {e}")
    await callback.answer()

@router.callback_query(F.data == "products")
async def send_products(callback: types.CallbackQuery):
    try:
        await bot.send_document(callback.from_user.id, FSInputFile('/var/www/netwell/files/products.pdf'))
    except Exception as e:
        await callback.message.answer(f"Ошибка отправки файла: {e}")
    await callback.answer()

@router.callback_query(F.data == "brandbook")
async def send_brandbook(callback: types.CallbackQuery):
    try:
        await bot.send_document(callback.from_user.id, FSInputFile('/var/www/netwell/files/brandbook.pdf'))
    except Exception as e:
        await callback.message.answer(f"Ошибка отправки файла: {e}")
    await callback.answer()

@router.callback_query(F.data == "logo")
async def send_logo(callback: types.CallbackQuery):
    try:
        await bot.send_document(callback.from_user.id, FSInputFile('/var/www/netwell/files/logo.zip'))
    except Exception as e:
        await callback.message.answer(f"Ошибка отправки файла: {e}")
    await callback.answer()

# Справочник (все контакты)
@router.callback_query(F.data == "contacts")
async def send_contacts(callback: types.CallbackQuery):
    users = await search_user("%")  # Все
    if users:
        text = "\n".join([f"{u['name']}, {u['phone']}, {u['email']}, {u['corporate']}, {u['office']}, {u['about']}" for u in users])
        await callback.message.answer(text[:4096])  # Лимит Telegram
    else:
        await callback.message.answer("Справочник пуст.")
    await back_to_menu(callback)

# Поиск контактов
@router.callback_query(F.data == "search")
async def search_start(callback: types.CallbackQuery, state: FSMContext):
    await state.set_state(SearchState.search)
    await callback.message.answer("Введите Имя и Фамилию")
    await callback.answer()

@router.message(SearchState.search)
async def process_search(message: types.Message, state: FSMContext):
    results = await search_user(message.text.strip())
    if results:
        text = "\n".join([f"{r['name']}, {r['phone']}, {r['email']}, {r['corporate']}, {r['office']}, {r['about']}" for r in results])
        await message.answer(text[:4096])
    else:
        await message.answer("Контакт не найден.")
    await state.clear()

# Календарь мероприятий
@router.callback_query(F.data == "event")
async def event_menu(callback: types.CallbackQuery):
    kb = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text="Корпоративные", callback_data="corporative")],
        [InlineKeyboardButton(text="Обучения", callback_data="study")],
        [InlineKeyboardButton(text="Месяц", callback_data="month")],
        [InlineKeyboardButton(text="Назад в меню", callback_data="back")]
    ])
    await callback.message.answer("Выберите тип:", reply_markup=kb)
    await callback.answer()

@router.callback_query(F.data == "corporative")
async def send_corporative(callback: types.CallbackQuery):
    events = await get_events('corporative')
    text = "\n".join([f"{e['date']}: {e['description']}" for e in events]) or "Нет событий."
    await callback.message.answer(text)
    await callback.answer()

@router.callback_query(F.data == "study")
async def send_study(callback: types.CallbackQuery):
    events = await get_events('study')
    text = "\n".join([f"{e['date']}: {e['description']}" for e in events]) or "Нет обучений."
    await callback.message.answer(text)
    await callback.answer()

@router.callback_query(F.data == "month")
async def send_month(callback: types.CallbackQuery):
    events = await get_events('month')  # Или добавь фильтр по дате в query
    text = "\n".join([f"{e['date']}: {e['description']}" for e in events]) or "Нет активностей."
    await callback.message.answer(text)
    await callback.answer()

# Админ-команды (рассылка и блокировка)
@router.message(Command("admin_broadcast"))
async def admin_broadcast(message: types.Message):
    if message.from_user.id != ADMIN_ID:
        return
    parts = message.text.split(maxsplit=2)
    if len(parts) < 3:
        await message.answer("Формат: /admin_broadcast [Москва|Питер|ВСЕ] \"Текст\"")
        return
    city, text = parts[1], parts[2]
    if city == 'ВСЕ':
        await broadcast_all(text)
    else:
        await broadcast_by_city(city, text)
    await message.answer("Рассылка отправлена!")

@router.message(Command("block"))
async def admin_block(message: types.Message):
    if message.from_user.id != ADMIN_ID:
        return
    parts = message.text.split()
    if len(parts) < 2:
        await message.answer("Формат: /block <user_id>")
        return
    try:
        uid = int(parts[1])
        async with pool.acquire() as conn:
            async with conn.cursor() as cur:
                await cur.execute("UPDATE users SET blocked = TRUE WHERE user_id = %s", (uid,))
        await message.answer("Заблокирован!")
    except ValueError:
        await message.answer("Неверный ID.")

# Webhook setup (раскомментируй для продакшена)
async def on_startup(_):
    await init_db()
    webhook_url = f"https://netwell.nsforge.link{WEBHOOK_PATH}"
    await bot.set_webhook(url=webhook_url, secret_token=WEBHOOK_SECRET)
    logging.info(f"Webhook установлен: {webhook_url}")

async def on_shutdown(_):
    await bot.delete_webhook()
    if pool:
        pool.close()
        await pool.wait_closed()

app = web.Application()
SimpleRequestHandler(dispatcher=dp, bot=bot).register(app, path=WEBHOOK_PATH)
setup_application(app, dp, bot=bot)

async def main():
    await init_db()
    await on_startup(None)
    logging.info("Starting uvicorn webhook")
    await web.run_app(app, host='0.0.0.0', port=8080, shutdown_timeout=60.0)

if __name__ == "__main__":
    asyncio.run(main())
