"""
Hotel Finder Backend — Server Entry Point
==========================================
This orchestrator automatically tracks the operating system, verifies system 
dependencies, creates/loads a Python virtual environment, installs standard 
and OS-specific dependencies, runs migrations, executes commands, and starts the server.
"""

import os
import sys
import time
import platform
import subprocess
import multiprocessing
import threading

multiprocessing.freeze_support()

# ─────────────────────────────────────────────────────────────
# OS and Environment Bootstrapping
# ─────────────────────────────────────────────────────────────

def in_venv():
    """Returns True if running inside a virtual environment."""
    return sys.prefix != sys.base_prefix

def get_os_type():
    system = platform.system().lower()
    if system == "windows":
        return "windows"
    elif system == "darwin":
        return "mac"
    elif system == "linux":
        return "linux"
    return "other"

def check_linux_distribution():
    if not os.path.exists('/etc/os-release'):
        return "unknown"
    with open('/etc/os-release') as f:
        content = f.read().lower()
        if 'debian' in content or 'ubuntu' in content:
            return 'debian'
        elif 'fedora' in content or 'centos' in content or 'rhel' in content or 'redhat' in content:
            return 'rpm'
    return "unknown"

def print_system_deps_warning(linux_distro):
    if linux_distro == 'debian':
        print("  [Linux Setup] Tip: Please ensure you have Python headers and MySQL dev tools installed.")
        print("                Run: sudo apt-get install python3-dev python3-venv default-libmysqlclient-dev build-essential")
    elif linux_distro == 'rpm':
        print("  [Linux Setup] Tip: Please ensure you have Python headers and MySQL dev tools installed.")
        print("                Run: sudo yum install python3-devel mysql-devel gcc")

def create_os_requirements_files():
    """Create missing OS-specific requirements files."""
    files = {
        "requirements_windows.txt": "# Windows specific requirements\npywin32\n",
        "requirements_linux.txt": "# Linux specific requirements\n",
        "requirements_mac.txt": "# MacOS specific requirements\n",
    }
    for file_name, content in files.items():
        # Ensure they are created in the base directory
        file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), file_name)
        if not os.path.exists(file_path):
            with open(file_path, 'w') as f:
                f.write(content)
            print(f"  [OK] Auto-created {file_name}")

def orchestrate_startup():
    """First phase: Outside the virtualenv (or verifying if it doesn't exist)."""
    os_type = get_os_type()
    print(f"  [*] Detected OS: {os_type.capitalize()}")
    
    if os_type == "linux":
        distro = check_linux_distribution()
        print(f"  [*] Detected Linux Distro: {distro.capitalize()}")
        print_system_deps_warning(distro)

    # 1. Create missing OS specific requirements files
    create_os_requirements_files()
    
    # 2. Virtual Environment check and creation
    base_dir = os.path.dirname(os.path.abspath(__file__))
    venv_dir = os.path.join(base_dir, 'venv')
    
    if not os.path.exists(venv_dir):
        print("  [*] Virtual environment not found. Creating 'venv'...")
        subprocess.check_call([sys.executable, "-m", "venv", "venv"])
        print("  [OK] Virtual environment created.")
    else:
        print("  [OK] Virtual environment 'venv' found.")

    # 3. Determine the Python executable inside the newly/already created venv
    if os_type == "windows":
        venv_python = os.path.join(venv_dir, "Scripts", "python.exe")
    else:
        venv_python = os.path.join(venv_dir, "bin", "python")

    if not os.path.exists(venv_python):
        print(f"  [FAIL] Could not find the Python executable at {venv_python}")
        sys.exit(1)

    print("  [*] Re-launching execution from inside the virtual environment...")
    
    # 4. Relaunch the exact same script but using the Venv's python
    try:
        # sys.argv retains any passed arguments like custom flags
        sys.exit(subprocess.call([venv_python, __file__] + sys.argv[1:]))
    except KeyboardInterrupt:
        sys.exit(0)


# ─────────────────────────────────────────────────────────────
# Execution inside Virtual Environment Phase
# ─────────────────────────────────────────────────────────────

def get_base_dir():
    if getattr(sys, 'frozen', False):
        return os.path.dirname(sys.executable)
    return os.path.dirname(os.path.abspath(__file__))

def get_env_path():
    return os.path.join(get_base_dir(), '.env')

def load_environment():
    # Defer loading dotenv to ensure it is installed first via pip below
    try:
        from dotenv import load_dotenv
    except ImportError:
        print("  [FAIL] dotenv is not installed yet!")
        return
        
    env_path = get_env_path()
    if os.path.exists(env_path):
        print(f"  [OK] Loading environment from {env_path}")
        load_dotenv(env_path)
    else:
        print(f"  [!!] .env file not found at {env_path}")

def install_dependencies():
    print("  [..] Activating pip resolution in virtual environment...")
    os_type = get_os_type()
    
    # Install main requirements
    req_file = os.path.join(get_base_dir(), "requirements.txt")
    if os.path.exists(req_file):
        print(f"  [..] Installing dependencies from {req_file}...")
        subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", req_file, "--quiet"])
    
    # Install OS-specific requirements
    os_req_file = os.path.join(get_base_dir(), f"requirements_{os_type}.txt")
    if os.path.exists(os_req_file):
        print(f"  [..] Installing OS-specific dependencies from {os_req_file}...")
        subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", os_req_file, "--quiet"])

def wait_for_database(max_retries=10, delay=3):
    from django.db import connection
    from django.db.utils import OperationalError
    for attempt in range(1, max_retries + 1):
        try:
            connection.ensure_connection()
            print("  [OK] Database connection established")
            return True
        except (OperationalError, Exception) as e:
            if attempt < max_retries:
                print(f"  [..] Database not ready (attempt {attempt}/{max_retries}): {e}")
                print(f"       Retrying in {delay}s ...")
                time.sleep(delay)
            else:
                print(f"  [FAIL] Could not connect to Database after {max_retries} attempts.")
                print(f"         Last error: {e}")
                return False

def initialize_database():
    from django.core.management import execute_from_command_line
    from django.contrib.auth import get_user_model

    print("  [..] Applying database migrations ...")
    try:
        execute_from_command_line(["run_server", "makemigrations"])
        execute_from_command_line(["run_server", "migrate", "--run-syncdb"])
        print("  [OK] Migrations applied successfully")
    except Exception as e:
        print(f"  [FAIL] Migration error: {e}")
        return

    # Create superuser if provided in .env
    User = get_user_model()
    su_username = os.getenv('SUPERUSER_USERNAME')
    su_password = os.getenv('SUPERUSER_PASSWORD')
    su_email = os.getenv('SUPERUSER_EMAIL', 'admin@example.com')

    if su_username and su_password:
        if not User.objects.filter(username=su_username).exists():
            print(f"  [..] Creating superuser '{su_username}' ...")
            User.objects.create_superuser(
                username=su_username,
                email=su_email,
                password=su_password,
                role='ADMIN'
            )
            print(f"  [OK] Superuser '{su_username}' created")
        else:
            print(f"  [OK] Superuser '{su_username}' already exists")

def run_pre_runserver_commands():
    print("  [..] Checking for any commands to run before starting the server...")
    # Add any predefined management commands here
    print("  [OK] Executed predefined commands successfully.")

def ensure_directories():
    base = get_base_dir()
    for folder in ['media', 'logs']:
        path = os.path.join(base, folder)
        os.makedirs(path, exist_ok=True)

def print_banner(port):
    print()
    print("  =====================================================")
    print("  |        Hotel Finder API & Management Server       |")
    print("  |        Vaiditech Solutions Pvt Ltd                 |")
    print("  =====================================================")
    print(f"  |  Server running on: http://localhost:{port}/         |")
    print(f"  |  Admin panel:       http://localhost:{port}/admin/   |")
    print(f"  |  API docs:          http://localhost:{port}/api/swagger/|")
    print("  |                                                   |")
    print("  |  Press Ctrl+C to stop the server                  |")
    print("  =====================================================")
    print()


def execute_in_venv():
    print()
    print("  Hotel Finder Server - Sub-Phase Execution (In VirtualEnv)")
    print("  --------------------------------------------------------")
    
    # Ensure OS specific requirements files exist
    create_os_requirements_files()

    # 1. Install pip packages required by the application
    install_dependencies()

    # 2. Only after packages are installed, we can safely import Django
    import django
    
    # 3. Environment variables
    load_environment()

    # 4. Django settings setup
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hotelfinder.settings')
    django.setup()

    # 5. Dirs
    ensure_directories()

    # 6. Database Connection check
    if not wait_for_database(max_retries=10, delay=3):
        print("  Server cannot start without a database connection.")
        input("  Press Enter to exit ...")
        sys.exit(1)

    # 7. Database initialisation (makemigrations, migrate, createsuperuser)
    try:
        initialize_database()
    except Exception as e:
        print(f"  [!!] Database init warning: {e}")

    # 8. Any predefined commands requested before starting
    run_pre_runserver_commands()

    # 9. Optional Static file collection
    from django.core.management import execute_from_command_line
    is_debug = os.getenv("DEBUG", "False").lower() == "true"
    
    if not is_debug:
        try:
            print("  [..] Collecting static files ...")
            execute_from_command_line(["run_server", "collectstatic", "--noinput"])
            print("  [OK] Static files collected")
        except Exception:
            pass
    else:
        print("  [OK] Skipping collectstatic (Debug Mode)")

    # 10. Start Server (Smart Mode)
    port = int(os.getenv("PORT", 8001))

    if is_debug:
        run_smart_dev_server(port)
    else:
        try:
            from hotelfinder.wsgi import application
            run_prod_server(port, application)
        except ImportError:
            print("  [FAIL] Failed to import WSGI application.")
            sys.exit(1)

def run_prod_server(port, application):
    try:
        from waitress import serve
        print_banner(port)
        serve(application, host='0.0.0.0', port=port, threads=8)
    except Exception as e:
        print(f"\n  [FAIL] Production server crashed: {e}")
        input("\n  Press Enter to exit ...")
        sys.exit(1)

def run_smart_dev_server(port):
    import subprocess
    import signal
    
    print(f"  [*] Starting Smart Dev Server (Detecting models.py changes)...")
    
    server_proc = None
    
    def start_server():
        nonlocal server_proc
        if server_proc:
            if os.name == 'nt':
                server_proc.terminate()
            else:
                os.killpg(os.getpgid(server_proc.pid), signal.SIGTERM)
        
        print("\n  [RESTART] Starting manage.py runserver ...")
        # We use a new process group on Linux to ensure all children are killed
        if os.name != 'nt':
            server_proc = subprocess.Popen(
                [sys.executable, 'manage.py', 'runserver', f'0.0.0.0:{port}'],
                preexec_fn=os.setsid
            )
        else:
            server_proc = subprocess.Popen(
                [sys.executable, 'manage.py', 'runserver', f'0.0.0.0:{port}']
            )

    def on_model_change():
        print("\n  [DETECTED] Change in models.py!")
        print("  [..] Restarting entire orchestrator to apply changes...")
        try:
            if server_proc:
                if os.name == 'nt':
                    server_proc.terminate()
                else:
                    os.killpg(os.getpgid(server_proc.pid), signal.SIGTERM)
            
            # Re-execute the current script with the same arguments
            os.execv(sys.executable, [sys.executable] + sys.argv)
        except Exception as e:
            print(f"  [ERROR] Restart failed: {e}")
            sys.exit(1)

    # Start the watcher
    watcher = ModelWatcher(get_base_dir(), on_model_change)
    watcher.start()

    # Initial start
    print_banner(port)
    start_server()

    try:
        while True:
            time.sleep(1)
            if server_proc and server_proc.poll() is not None:
                # Server exited unexpectedly or was killed
                break
    except KeyboardInterrupt:
        print("\n  [*] Stopping Dev Server...")
        if server_proc:
            if os.name == 'nt':
                server_proc.terminate()
            else:
                os.killpg(os.getpgid(server_proc.pid), signal.SIGTERM)
        sys.exit(0)

class ModelWatcher(threading.Thread):
    def __init__(self, base_dir, callback):
        super().__init__(daemon=True)
        self.base_dir = base_dir
        self.callback = callback
        self.last_mtimes = self._get_models_mtimes()

    def _get_models_mtimes(self):
        mtimes = {}
        for root, dirs, files in os.walk(self.base_dir):
            if 'venv' in root or '.git' in root: # Skip venv and git
                continue
            if 'models.py' in files:
                path = os.path.join(root, 'models.py')
                try:
                    mtimes[path] = os.path.getmtime(path)
                except OSError:
                    pass
        return mtimes

    def run(self):
        while True:
            time.sleep(1)
            try:
                current_mtimes = self._get_models_mtimes()
                if current_mtimes != self.last_mtimes:
                    self.last_mtimes = current_mtimes
                    self.callback()
            except Exception:
                pass


# ─────────────────────────────────────────────────────────────
# Entry Point
# ─────────────────────────────────────────────────────────────

def main():
    if not in_venv():
        # First phase executed by the system python
        orchestrate_startup()
    else:
        # Second phase executed by the virtual environment's python
        execute_in_venv()

if __name__ == '__main__':
    main()
