"""
Xlancer Bot -- Unified Runner & Manager

Commands:
  python runner.py           -> Start/Restart the bot
  python runner.py --stop    -> Stop the running bot instance
  python runner.py --restart -> Restart the bot instance
  python runner.py --status  -> Check if the bot is currently running
  python runner.py --reset   -> Reset database (DANGER: wipes all data)
  python runner.py --update  -> Force-upgrade all pip dependencies
  python runner.py --help    -> Show this help
"""

import os
import sys

# Fix Windows terminal encoding so Farsi/Unicode prints correctly
if sys.stdout.encoding and sys.stdout.encoding.lower() != 'utf-8':
    try:
        sys.stdout.reconfigure(encoding='utf-8', errors='replace')
        sys.stderr.reconfigure(encoding='utf-8', errors='replace')
    except Exception:
        pass

import shutil
import asyncio
import subprocess
import time
import argparse
from datetime import datetime

# ══════════════════════════════════════════
# Terminal Colors
# ══════════════════════════════════════════
class C:
    HEADER  = '\033[95m'
    BLUE    = '\033[94m'
    CYAN    = '\033[96m'
    GREEN   = '\033[92m'
    YELLOW  = '\033[93m'
    RED     = '\033[91m'
    RESET   = '\033[0m'
    BOLD    = '\033[1m'
    DIM     = '\033[2m'

if os.name == 'nt':
    os.system('color')

def log(msg, color=C.BLUE, level="INFO"):
    ts = datetime.now().strftime("%H:%M:%S")
    print(f"{C.DIM}[{ts}]{C.RESET} {C.BOLD}[{level}]{C.RESET} {color}{msg}{C.RESET}")

def banner():
    print(f"\n{C.HEADER}{C.BOLD}")
    print("  ██╗  ██╗██╗      █████╗ ███╗   ██╗ ██████╗███████╗██████╗ ")
    print("  ╚██╗██╔╝██║     ██╔══██╗████╗  ██║██╔════╝██╔════╝██╔══██╗")
    print("   ╚███╔╝ ██║     ███████║██╔██╗ ██║██║     █████╗  ██████╔╝")
    print("   ██╔██╗ ██║     ██╔══██║██║╚██╗██║██║     ██╔══╝  ██╔══██╗")
    print("  ██╔╝ ██╗███████╗██║  ██║██║ ╚████║╚██████╗███████╗██║  ██║")
    print("  ╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝╚═╝  ╚═══╝ ╚═════╝╚══════╝╚═╝  ╚═╝")
    print(f"{C.RESET}{C.CYAN}              Freelance Platform Bot — Runner v2.0{C.RESET}")
    print("─" * 65)

# ══════════════════════════════════════════
# Dependency Management
# ══════════════════════════════════════════
def update_dependencies(force=False):
    req_file = "requirements.txt"
    if not os.path.exists(req_file):
        log(f"'{req_file}' not found. Skipping.", C.YELLOW, "WARN")
        return True

    log("Updating pip itself...", C.CYAN)
    subprocess.run(
        [sys.executable, "-m", "pip", "install", "--upgrade", "pip", "--quiet"],
        check=False
    )

    if force:
        log("Force-upgrading all dependencies...", C.CYAN, "UPDATE")
        cmd = [sys.executable, "-m", "pip", "install", "--upgrade", "-r", req_file]
    else:
        log("Installing/checking dependencies...", C.CYAN)
        cmd = [sys.executable, "-m", "pip", "install", "-r", req_file, "--quiet"]

    try:
        subprocess.run(cmd, check=True)
        log("Dependencies are up to date!", C.GREEN, "OK")
        return True
    except subprocess.CalledProcessError:
        log("Dependency installation failed. Check your network or requirements.txt", C.RED, "ERROR")
        return False

def check_dependencies():
    return update_dependencies(force=False)

# ══════════════════════════════════════════
# Database Reset
# ══════════════════════════════════════════
async def reset_database():
    try:
        from config import DB_PATH
        from database import db
    except ImportError:
        log("Cannot import project modules. Run from the project root directory.", C.RED, "FATAL")
        sys.exit(1)

    print(f"\n{C.RED}{C.BOLD}{'═'*60}")
    print("  ⚠️   D A N G E R   Z O N E   ⚠️".center(60))
    print(f"{'═'*60}{C.RESET}\n")

    log(f"Target database: {DB_PATH}", C.YELLOW, "WARN")
    log("This will WIPE all users, projects, bids, transactions, and financial records!", C.YELLOW, "WARN")

    # Show file size if exists
    if os.path.exists(DB_PATH):
        size_kb = os.path.getsize(DB_PATH) / 1024
        log(f"Current database size: {size_kb:.1f} KB", C.YELLOW, "INFO")

    print()
    confirm = input(f"{C.RED}{C.BOLD}Type 'YES' to confirm full reset (anything else aborts): {C.RESET}")

    if confirm.strip() != 'YES':
        print(f"\n{C.BLUE}Aborted. Database was NOT touched.{C.RESET}\n")
        return

    print()
    log("Starting reset procedure...", C.YELLOW)

    # Backup old database
    if os.path.exists(DB_PATH):
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_path = f"{DB_PATH}.bak_{timestamp}"
        try:
            shutil.copy2(DB_PATH, backup_path)
            log(f"Backup saved → {backup_path}", C.GREEN, "BACKUP")
            os.remove(DB_PATH)
            log("Old database removed.", C.GREEN, "DONE")
        except Exception as e:
            log(f"Backup/remove failed: {e}", C.RED, "ERROR")
            sys.exit(1)
    else:
        log(f"No existing database found at '{DB_PATH}'. A fresh one will be created.", C.YELLOW, "INFO")

    # Also reset FSM storage if present
    fsm_path = "fsm_storage.sqlite"
    if os.path.exists(fsm_path):
        try:
            os.remove(fsm_path)
            log("FSM storage cleared.", C.GREEN, "DONE")
        except Exception as e:
            log(f"Could not clear FSM storage: {e}", C.YELLOW, "WARN")

    # Re-initialize schema
    log("Re-initializing database schema...", C.CYAN)
    try:
        await db.init_db()
        log("Schema initialized successfully.", C.GREEN, "SUCCESS")
    except Exception as e:
        log(f"Schema init failed: {e}", C.RED, "ERROR")
        sys.exit(1)

    print()
    print(f"{C.GREEN}{C.BOLD}{'─'*60}")
    print("  🎉  Database reset COMPLETE!")
    print(f"{'─'*60}{C.RESET}")
    print(f"  Start the bot with: {C.CYAN}python runner.py{C.RESET}\n")

# ══════════════════════════════════════════
# Bot Runner
# ══════════════════════════════════════════
def is_bot_running():
    pid_file = "bot.pid"
    if os.path.exists(pid_file):
        try:
            with open(pid_file, 'r') as f:
                pid = int(f.read().strip())
            # Check if PID exists and is active
            if os.name == 'nt':
                cmd = f"tasklist /FI \"PID eq {pid}\""
                output = subprocess.check_output(cmd, shell=True, text=True, errors='ignore', stderr=subprocess.DEVNULL)
                if str(pid) in output:
                    return pid
            else:
                try:
                    os.kill(pid, 0)
                    return pid
                except OSError:
                    pass
        except Exception:
            pass

    # System-wide scan fallback
    if os.name == 'nt':
        try:
            cmd = 'wmic process where "CommandLine like \'%bot.py%\' and Name like \'%python%\'" get ProcessID,CommandLine'
            output = subprocess.check_output(cmd, shell=True, text=True, errors='ignore', stderr=subprocess.DEVNULL)
            lines = output.strip().split('\n')
            current_pid = os.getpid()
            for line in lines[1:]:
                parts = line.strip().split()
                if not parts:
                    continue
                pid_candidate = parts[-1]
                if pid_candidate.isdigit():
                    pid_num = int(pid_candidate)
                    if pid_num != current_pid:
                        return pid_num
        except Exception:
            pass
    else:
        try:
            output = subprocess.check_output(["pgrep", "-f", "python.*bot.py"], text=True, errors='ignore', stderr=subprocess.DEVNULL)
            pids = [int(p) for p in output.strip().split() if p.isdigit()]
            current_pid = os.getpid()
            for p in pids:
                if p != current_pid:
                    return p
        except Exception:
            pass
    return None

def kill_existing_bot_processes():
    pid_file = "bot.pid"
    killed_any = False
    
    # 1. Kill using saved PID file
    if os.path.exists(pid_file):
        try:
            with open(pid_file, 'r') as f:
                pid = int(f.read().strip())
            
            # Check if PID is active
            is_active = False
            if os.name == 'nt':
                cmd = f"tasklist /FI \"PID eq {pid}\""
                output = subprocess.check_output(cmd, shell=True, text=True, errors='ignore', stderr=subprocess.DEVNULL)
                if str(pid) in output:
                    is_active = True
            else:
                try:
                    os.kill(pid, 0)
                    is_active = True
                except OSError:
                    pass
            
            if is_active:
                log(f"Terminating active bot instance (PID: {pid})...", C.YELLOW, "KILL")
                if os.name == 'nt':
                    subprocess.run(["taskkill", "/F", "/PID", str(pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
                else:
                    os.kill(pid, 9)
                killed_any = True
                time.sleep(0.5)
            else:
                log(f"Stale PID file found (PID: {pid}), cleaning up.", C.DIM)
        except Exception as e:
            log(f"PID cleanup note: {e}", C.DIM)
            
    # 2. System-wide scan for any processes running 'bot.py'
    if os.name == 'nt':
        try:
            cmd = 'wmic process where "CommandLine like \'%bot.py%\' and Name like \'%python%\'" get ProcessID,CommandLine'
            output = subprocess.check_output(cmd, shell=True, text=True, errors='ignore', stderr=subprocess.DEVNULL)
            lines = output.strip().split('\n')
            current_pid = os.getpid()
            for line in lines[1:]:
                parts = line.strip().split()
                if not parts:
                    continue
                pid_candidate = parts[-1]
                if pid_candidate.isdigit():
                    pid_num = int(pid_candidate)
                    if pid_num != current_pid:
                        log(f"Terminating duplicate bot process (PID: {pid_num})...", C.YELLOW, "KILL")
                        subprocess.run(["taskkill", "/F", "/PID", str(pid_num)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
                        killed_any = True
            time.sleep(0.5)
        except Exception as e:
            pass
    else:
        try:
            output = subprocess.check_output(["pgrep", "-f", "python.*bot.py"], text=True, errors='ignore', stderr=subprocess.DEVNULL)
            pids = [int(p) for p in output.strip().split() if p.isdigit()]
            current_pid = os.getpid()
            for p in pids:
                if p != current_pid:
                    log(f"Killing duplicate bot process (PID: {p})...", C.YELLOW, "KILL")
                    os.kill(p, 9)
                    killed_any = True
            time.sleep(0.5)
        except Exception:
            try:
                subprocess.run(["pkill", "-f", "python.*bot.py"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
                killed_any = True
            except Exception:
                pass
            
    if os.path.exists(pid_file):
        try: os.remove(pid_file)
        except: pass

    return killed_any

def run_bot():
    bot_file = "bot.py"
    if not os.path.exists(bot_file):
        log(f"'{bot_file}' not found in current directory!", C.RED, "FATAL")
        sys.exit(1)

    # Clear duplicate bot instances
    kill_existing_bot_processes()

    log(f"Launching {bot_file}...", C.CYAN)
    print("─" * 65 + "\n")

    try:
        process = subprocess.Popen([sys.executable, bot_file])
        
        # Save PID to file
        with open("bot.pid", "w") as f:
            f.write(str(process.pid))
            
        process.wait()
    except KeyboardInterrupt:
        print(f"\n{'─'*65}")
        log("Bot stopped by user (Ctrl+C).", C.YELLOW, "STOP")
    except Exception as e:
        print(f"\n{'─'*65}")
        log(f"Bot crashed unexpectedly: {e}", C.RED, "CRASH")
    finally:
        # Clean up PID file
        if os.path.exists("bot.pid"):
            try: os.remove("bot.pid")
            except: pass
        log("Runner exited.", C.BLUE, "EXIT")

# ══════════════════════════════════════════
# Entry Point
# ══════════════════════════════════════════
if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Xlancer Bot Runner & Manager",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=__doc__
    )
    parser.add_argument("--reset",   action="store_true", help="Reset the database (DANGER: wipes all data)")
    parser.add_argument("--update",  action="store_true", help="Force-upgrade all dependencies from requirements.txt")
    parser.add_argument("--stop",    action="store_true", help="Stop the running bot process")
    parser.add_argument("--restart", action="store_true", help="Restart the bot (stop existing and start a new one)")
    parser.add_argument("--status",  action="store_true", help="Check the bot's status")
    args = parser.parse_args()

    banner()

    if args.status:
        pid = is_bot_running()
        if pid:
            log(f"Bot is RUNNING (PID: {pid}).", C.GREEN, "STATUS")
        else:
            log("Bot is STOPPED.", C.YELLOW, "STATUS")
        sys.exit(0)

    if args.stop:
        pid = is_bot_running()
        if pid:
            log(f"Stopping bot (PID: {pid})...", C.CYAN, "STOP")
            kill_existing_bot_processes()
            log("Bot stopped successfully.", C.GREEN, "OK")
        else:
            log("Bot is not running.", C.YELLOW, "INFO")
        sys.exit(0)

    if args.restart:
        pid = is_bot_running()
        if pid:
            log(f"Stopping bot (PID: {pid})...", C.CYAN, "STOP")
            kill_existing_bot_processes()
        log("Starting the bot...", C.CYAN, "START")
        if check_dependencies():
            time.sleep(0.5)
            run_bot()
        else:
            log("Cannot start bot due to dependency issues.", C.RED, "ABORT")
            sys.exit(1)
        sys.exit(0)

    if args.reset:
        # Database reset mode
        asyncio.run(reset_database())
        sys.exit(0)

    if args.update:
        # Force update all libs
        log("Force-upgrading all dependencies...", C.CYAN, "UPDATE")
        success = update_dependencies(force=True)
        if success:
            log("All packages upgraded successfully!", C.GREEN, "DONE")
        else:
            log("Some packages could not be upgraded.", C.RED, "ERROR")
            sys.exit(1)
        sys.exit(0)

    # Normal start: check deps then run bot
    time.sleep(0.3)
    if check_dependencies():
        time.sleep(0.5)
        run_bot()
    else:
        log("Cannot start bot due to dependency issues.", C.RED, "ABORT")
        sys.exit(1)
