#!/usr/bin/env python3
"""
deploy.py — Скрипт синхронизации и деплоя на сервер 46.30.191.186

Использование:
  python deploy.py              — полный деплой (код + БД бекап)
  python deploy.py --code       — только код (server.js, bot, admin-panel)
  python deploy.py --db-push    — загрузить локальную БД на сервер
  python deploy.py --db-pull    — скачать БД с сервера локально
  python deploy.py --frontend   — пересобрать фронтенд на сервере
  python deploy.py --status     — проверить статус сервера
"""
import paramiko, sys, os, time, hashlib, argparse, shutil
from datetime import datetime

sys.stdout.reconfigure(encoding='utf-8', errors='replace')

# ============ CONFIG ============
SERVER_IP = '77.91.97.221'
SSH_KEY = r'C:\Users\papus\.ssh\id_ed25519'
REMOTE_DIR = '/var/www/wallet-app-tr'
LOCAL_DIR = r'C:\Users\papus\OneDrive\Desktop\TR'
BACKUP_DIR = os.path.join(LOCAL_DIR, 'backups')

FILES_TO_SYNC = {
    'server.js': f'{REMOTE_DIR}/server.js',
    'telegram-bot-telegraf.js': f'{REMOTE_DIR}/telegram-bot-telegraf.js',
    'bot-config.js': f'{REMOTE_DIR}/bot-config.js',
    'admin-panel.html': f'{REMOTE_DIR}/dist/admin-panel.html',
    'admin-panel-auth.html': f'{REMOTE_DIR}/dist/admin-panel-auth.html',
}
# ================================

def connect():
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    try:
        key = paramiko.Ed25519Key.from_private_key_file(SSH_KEY)
        client.connect(SERVER_IP, username='root', pkey=key, timeout=30)
    except Exception:
        # fallback to password auth
        client.connect(SERVER_IP, username='root', password='NQDuz3qX6Yevo', timeout=30)
    return client

def run(client, cmd, timeout=30):
    _, stdout, stderr = client.exec_command(cmd, timeout=timeout)
    out = stdout.read().decode(errors='replace').strip()
    err = stderr.read().decode(errors='replace').strip()
    return out, err

def md5_file(path):
    with open(path, 'rb') as f:
        return hashlib.md5(f.read()).hexdigest()

def ensure_backup_dir():
    os.makedirs(BACKUP_DIR, exist_ok=True)

def deploy_code(client):
    """Синхронизирует код: только изменённые файлы"""
    print("\n📦 Синхронизация кода...")
    sftp = client.open_sftp()
    changed = []

    for local_name, remote_path in FILES_TO_SYNC.items():
        local_path = os.path.join(LOCAL_DIR, local_name)
        if not os.path.exists(local_path):
            print(f"  ⏭️  {local_name} — нет локально, пропуск")
            continue

        local_hash = md5_file(local_path)
        remote_hash, _ = run(client, f"md5sum {remote_path} 2>/dev/null | awk '{{print $1}}'")

        if local_hash == remote_hash:
            print(f"  ✅ {local_name} — синхронизирован")
        else:
            sftp.put(local_path, remote_path)
            changed.append(local_name)
            print(f"  📤 {local_name} — обновлён")

    sftp.close()

    if changed:
        needs_app_restart = any(f in changed for f in ['server.js'])
        needs_bot_restart = any(f in changed for f in ['telegram-bot-telegraf.js', 'bot-config.js'])

        if needs_app_restart:
            print("\n  🔄 Перезапуск wallet-app...")
            run(client, "pm2 restart wallet-app")
        if needs_bot_restart:
            print("  🔄 Перезапуск telegram-bot...")
            run(client, "pm2 restart telegram-bot")

        if needs_app_restart or needs_bot_restart:
            time.sleep(4)

        print(f"\n  📋 Обновлено файлов: {len(changed)}")
    else:
        print("\n  ✅ Все файлы уже синхронизированы")

    return changed

def db_pull(client):
    """Скачивает БД с сервера в локальный бэкап"""
    print("\n📥 Скачивание БД с сервера...")
    ensure_backup_dir()

    ts = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
    local_backup = os.path.join(BACKUP_DIR, f'wallets-{ts}.db')

    sftp = client.open_sftp()
    sftp.get(f'{REMOTE_DIR}/wallets.db', local_backup)
    sftp.close()

    size = os.path.getsize(local_backup)
    print(f"  ✅ Сохранено: {local_backup} ({size:,} bytes)")

    # Также обновляем рабочую копию
    shutil.copy2(local_backup, os.path.join(LOCAL_DIR, 'wallets-migrated.db'))
    print(f"  ✅ Обновлена рабочая копия wallets-migrated.db")
    return local_backup

def db_push(client):
    """Загружает локальную БД на сервер"""
    local_db = os.path.join(LOCAL_DIR, 'wallets-migrated.db')
    if not os.path.exists(local_db):
        print("  ❌ Локальная БД wallets-migrated.db не найдена!")
        return

    print("\n📤 Загрузка БД на сервер...")

    # Бэкап серверной БД
    ts = datetime.now().strftime('%Y%m%d_%H%M%S')
    run(client, f"cp {REMOTE_DIR}/wallets.db {REMOTE_DIR}/wallets.db.bak-{ts}")

    # Остановка, загрузка, запуск
    run(client, "pm2 stop all")
    time.sleep(2)
    run(client, f"rm -f {REMOTE_DIR}/wallets.db-wal {REMOTE_DIR}/wallets.db-shm")

    sftp = client.open_sftp()
    sftp.put(local_db, f'{REMOTE_DIR}/wallets.db')
    sftp.close()

    # Проверка
    integrity, _ = run(client, f"sqlite3 {REMOTE_DIR}/wallets.db 'PRAGMA integrity_check;'")
    if integrity != 'ok':
        print(f"  ❌ БД повреждена! Восстанавливаю из бэкапа...")
        run(client, f"cp {REMOTE_DIR}/wallets.db.bak-{ts} {REMOTE_DIR}/wallets.db")
    else:
        print(f"  ✅ БД загружена и проверена (integrity={integrity})")

    run(client, "pm2 start all")
    time.sleep(4)

def rebuild_frontend(client):
    """Пересобирает фронтенд на сервере"""
    print("\n🏗️  Пересборка фронтенда...")

    # Загрузка локальных исходников
    src_dir = os.path.join(LOCAL_DIR, 'frontend', 'src')
    if not os.path.exists(src_dir):
        print("  ❌ Папка frontend/src не найдена!")
        return

    sftp = client.open_sftp()
    uploaded = 0
    for root, dirs, files in os.walk(src_dir):
        for f in files:
            local_path = os.path.join(root, f)
            rel_path = os.path.relpath(local_path, LOCAL_DIR).replace('\\', '/')
            remote_path = f'{REMOTE_DIR}/{rel_path}'

            # Создаём директорию если нужно
            remote_dir = os.path.dirname(remote_path)
            try:
                sftp.stat(remote_dir)
            except:
                run(client, f"mkdir -p {remote_dir}")

            sftp.put(local_path, remote_path)
            uploaded += 1

    sftp.close()
    print(f"  📤 Загружено {uploaded} исходных файлов")

    print("  🔧 Запуск npm run build...")
    out, err = run(client, f"cd {REMOTE_DIR}/frontend && npm run build 2>&1", timeout=120)
    if 'error' in (out + err).lower() and 'warning' not in (out + err).lower():
        print(f"  ❌ Ошибка сборки: {(out + err)[-300:]}")
    else:
        print(f"  ✅ Фронтенд собран")
        # После сборки Vite очищает dist/, восстанавливаем admin-panel файлы
        for local_name, remote_path in FILES_TO_SYNC.items():
            if 'admin-panel' in local_name:
                local_path = os.path.join(LOCAL_DIR, local_name)
                if os.path.exists(local_path):
                    sftp2 = client.open_sftp()
                    sftp2.put(local_path, remote_path)
                    sftp2.close()
                    print(f"  📤 Восстановлен {local_name} в dist/")
        run(client, "systemctl reload nginx")

def show_status(client):
    """Показывает состояние сервера"""
    print("\n📊 Состояние сервера")
    print("=" * 50)

    out, _ = run(client, "pm2 jlist")
    import json
    try:
        procs = json.loads(out)
        for p in procs:
            name = p['name']
            status = p['pm2_env']['status']
            restarts = p['pm2_env']['restart_time']
            mem = p['monit']['memory'] // (1024*1024)
            print(f"  {'🟢' if status == 'online' else '🔴'} {name}: {status} (restarts: {restarts}, RAM: {mem}MB)")
    except:
        print(f"  PM2: {out[:200]}")

    wc, _ = run(client, f"sqlite3 {REMOTE_DIR}/wallets.db 'SELECT count(*) FROM wallets;'")
    uc, _ = run(client, f"sqlite3 {REMOTE_DIR}/wallets.db 'SELECT count(*) FROM users;'")
    print(f"\n  📁 БД: {wc} кошельков, {uc} пользователей")

    # Проверяем синхронизацию файлов
    print("\n  📋 Синхронизация файлов:")
    for local_name, remote_path in FILES_TO_SYNC.items():
        local_path = os.path.join(LOCAL_DIR, local_name)
        if not os.path.exists(local_path):
            continue
        local_hash = md5_file(local_path)
        remote_hash, _ = run(client, f"md5sum {remote_path} 2>/dev/null | awk '{{print $1}}'")
        synced = local_hash == remote_hash
        print(f"    {'✅' if synced else '❌'} {local_name}")

    out, _ = run(client, "curl -s -o /dev/null -w '%{http_code}' https://trustalpha.pro/")
    print(f"\n  🌐 HTTPS: {out}")

def main():
    parser = argparse.ArgumentParser(description='Deploy & Sync — trustalpha.pro')
    parser.add_argument('--code', action='store_true', help='Синхронизировать код')
    parser.add_argument('--db-push', action='store_true', help='Загрузить локальную БД на сервер')
    parser.add_argument('--db-pull', action='store_true', help='Скачать БД с сервера')
    parser.add_argument('--frontend', action='store_true', help='Пересобрать фронтенд')
    parser.add_argument('--status', action='store_true', help='Показать статус')
    args = parser.parse_args()

    # Если нет флагов — полный деплой (код + db-pull)
    full_deploy = not any([args.code, args.db_push, args.db_pull, args.frontend, args.status])

    print(f"🚀 Deploy — trustalpha.pro ({SERVER_IP})")
    print(f"   {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")

    client = connect()

    try:
        if args.status:
            show_status(client)
        elif full_deploy:
            deploy_code(client)
            db_pull(client)
            show_status(client)
        else:
            if args.code:
                deploy_code(client)
            if args.db_push:
                db_push(client)
            if args.db_pull:
                db_pull(client)
            if args.frontend:
                rebuild_frontend(client)
    finally:
        client.close()

    print(f"\n✅ Готово!")

if __name__ == '__main__':
    main()
