"""
Kiwy Signage Player - Windows Entry Point
------------------------------------------
Patches platform-specific code and environment for Windows before launching
the original Kivy-based signage player application.

Usage:
    python run_win.py          (for development/testing)
    run_win.exe                (after PyInstaller build)
"""

import ctypes
import os
import sys
import platform
import tempfile
import subprocess
import shutil
import logging
from pathlib import Path


def _show_error_box(title, message):
    """Show a Windows message box with the error (visible even without console)."""
    try:
        ctypes.windll.user32.MessageBoxW(0, message, title, 0x10)  # MB_ICONERROR
    except Exception:
        pass

# --- Ensure we are on Windows; warn if not ---
if platform.system() != 'Windows':
    print(f"WARNING: This entry point is designed for Windows. Detected: {platform.system()}")

# =====================================================================
# 1. Set Windows-compatible environment variables BEFORE Kivy imports
# =====================================================================

# Video driver: Use 'windib' or 'angle' (DirectX via ANGLE) for Windows
os.environ.setdefault('SDL_VIDEODRIVER', 'windows')
# Audio driver: DirectSound for Windows
os.environ.setdefault('SDL_AUDIODRIVER', 'directsound')
# Prevent screensaver
os.environ.setdefault('SDL_VIDEO_ALLOW_SCREENSAVER', '0')
# Video backend via ffpyplayer
os.environ.setdefault('KIVY_VIDEO', 'ffpyplayer')
os.environ.setdefault('KIVY_AUDIO', 'ffpyplayer')
os.environ.setdefault('FFPYPLAYER_CODECS', 'h264,h265,vp9,vp8')
# Kivy window backend: prefer SDL2 on Windows
os.environ.setdefault('KIVY_WINDOW', 'sdl2')
# OpenGL
os.environ.setdefault('KIVY_GL_BACKEND', 'angle_sdl2')

# =====================================================================
# 2. Patch the evdev import — it is Linux-only. We provide a dummy
#    module so that `from evdev import ...` will not crash on Windows.
# =====================================================================
class _DummyEvdev:
    """Fake evdev module that raises ImportError for all meaningful uses."""

    class InputDevice:
        def __init__(self, *a, **kw):
            raise ImportError("evdev is not available on Windows")

    class ecodes:
        EV_KEY = 1
        EV_ABS = 3

    def categorize(self, *a, **kw):
        raise ImportError("evdev is not available on Windows")

    def list_devices(self):
        return []


class _FakeEvdevInputDevice:
    pass


# Inject the fake evdev module into sys.modules so that main.py's
# `try: import evdev` succeeds but EVDEV_AVAILABLE stays False.
_evdev_dummy = _DummyEvdev()
sys.modules['evdev'] = _evdev_dummy
sys.modules['evdev.InputDevice'] = _FakeEvdevInputDevice

# =====================================================================
# 3. Provide a Windows implementation of screen activity signaling
#    We monkey-patch the SignagePlayer.signal_screen_activity method
#    after the class is defined but before it's used, by hooking into
#    the import machinery.
# =====================================================================

# We'll store a reference to the original module's signal_screen_activity
# so we can replace it after import.  This is done inside _patch_main().

def _windows_screen_activity(self, dt):
    """Windows alternative to Linux screen-keep-awake commands.

    Uses SetThreadExecutionState via ctypes to tell Windows to keep
    the display and system awake.
    """
    try:
        # ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED | ES_CONTINUOUS
        ES_CONTINUOUS = 0x80000000
        ES_SYSTEM_REQUIRED = 0x00000001
        ES_DISPLAY_REQUIRED = 0x00000002

        ctypes.windll.kernel32.SetThreadExecutionState(
            ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED
        )
    except Exception:
        pass  # non-critical


def _windows_find_browser():
    """Find Chrome or Edge executable on Windows for weblink support.

    Returns the path to the browser or None.
    """
    # Common install locations
    candidates = [
        # Chrome
        os.path.expandvars(r'%PROGRAMFILES%\Google\Chrome\Application\chrome.exe'),
        os.path.expandvars(r'%PROGRAMFILES(X86)%\Google\Chrome\Application\chrome.exe'),
        os.path.expandvars(r'%LOCALAPPDATA%\Google\Chrome\Application\chrome.exe'),
        # Edge
        os.path.expandvars(r'%PROGRAMFILES%\Microsoft\Edge\Application\msedge.exe'),
        os.path.expandvars(r'%PROGRAMFILES(X86)%\Microsoft\Edge\Application\msedge.exe'),
        os.path.expandvars(r'%LOCALAPPDATA%\Microsoft\Edge\Application\msedge.exe'),
    ]
    for path in candidates:
        if os.path.isfile(path):
            return path

    # Fallback: try PATH
    which = shutil.which('chrome') or shutil.which('msedge') or shutil.which('google-chrome')
    if which:
        return which

    return None


def _patch_main():
    """Patch the main module after import for Windows compatibility."""
    # ── CRITICAL: Override Linux env vars BEFORE importing main ─────
    # main.py's top-level code sets SDL_VIDEODRIVER=wayland,x11,dummy
    # and other Linux values. We MUST override these before main.py
    # gets imported, otherwise Kivy will initialize with the wrong
    # window provider and crash with SystemExit(1).
    os.environ['SDL_VIDEODRIVER'] = 'windows'
    os.environ['SDL_AUDIODRIVER'] = 'directsound'
    os.environ['KIVY_WINDOW'] = 'sdl2'
    os.environ['KIVY_GL_BACKEND'] = 'angle_sdl2'
    os.environ['KIVY_INPUTPROVIDERS'] = ''  # Let Kivy auto-detect
    os.environ['KIVY_VIDEO'] = 'ffpyplayer'
    os.environ['KIVY_AUDIO'] = 'ffpyplayer'

    # Now safe to import main.py — env vars are already Windows-correct
    import main as signage_main

    # Replace signal_screen_activity
    # IMPORTANT: Assign under BOTH the attribute name AND the function's own name.
    # Kivy's WeakMethod stores self.__func__.__name__ (= '_windows_screen_activity')
    # and later does getattr(instance, '_windows_screen_activity'). If we only
    # assign under 'signal_screen_activity', the weakref lookup fails with
    # AttributeError: 'SignagePlayer' object has no attribute '_windows_screen_activity'
    signage_main.SignagePlayer.signal_screen_activity = _windows_screen_activity
    signage_main.SignagePlayer._windows_screen_activity = _windows_screen_activity

    # Store a reference to the original play_weblink so we can wrap it
    _original_play_weblink = signage_main.SignagePlayer.play_weblink

    def _windows_play_weblink(self, url, duration):
        """Windows-compatible weblink handler using Chrome/Edge."""
        browser = _windows_find_browser()
        if not browser:
            from kivy.logger import Logger
            Logger.error(
                "SignagePlayer: Chrome/Edge not found. "
                "Cannot display weblink on Windows."
            )
            self.consecutive_errors += 1
            self._skip_to_next_media()
            return False

        from urllib.parse import urlparse
        scheme = urlparse(url).scheme.lower()
        if scheme not in ('http', 'https'):
            from kivy.logger import Logger
            Logger.warning(f"SignagePlayer: Refusing non-http(s) weblink: {url}")
            self.consecutive_errors += 1
            self._skip_to_next_media()
            return False

        import subprocess
        from kivy.logger import Logger
        from kivy.clock import Clock

        try:
            Logger.info(f"SignagePlayer: Opening weblink: {url} (browser: {browser})")
            self._kill_weblink_preload()

            # Launch in fullscreen / kiosk-like mode
            self._weblink_proc = subprocess.Popen([
                browser,
                '--kiosk',
                '--no-first-run',
                '--noerrdialogs',
                '--disable-infobars',
                '--incognito',
                '--no-default-browser-check',
                '--disable-session-crashed-bubble',
                '--disable-features=TranslateUI',
                url,
            ], shell=False)

            Clock.unschedule(self.next_media)
            self._start_inactivity_watchdog(duration)
            self.preload_next_media()
            return True

        except Exception as e:
            Logger.error(f"SignagePlayer: Error opening weblink: {e}")
            self.consecutive_errors += 1
            self._skip_to_next_media()
            return False

    # Replace weblink handling
    signage_main.SignagePlayer.play_weblink = _windows_play_weblink

    # Patch the _get_browser_target_size to always return a reasonable size on Windows
    def _windows_get_browser_target_size(self):
        try:
            from kivy.core.window import Window
            width, height = Window.size
            if width >= 1280 and height >= 720:
                return int(width), int(height)
        except Exception:
            pass
        return 1920, 1080

    signage_main.SignagePlayer._get_browser_target_size = _windows_get_browser_target_size

    # Patch _start_inactivity_watchdog for Windows — /dev/input does not exist
    _original_watchdog = signage_main.SignagePlayer._start_inactivity_watchdog

    def _windows_watchdog(self, duration):
        """Windows watchdog: uses a simple timer since /dev/input is not available.
        
        Falls back to a fixed timer that advances after 'duration' seconds.
        """
        from kivy.clock import Clock
        import threading

        self._stop_inactivity_watchdog()
        stop_event = threading.Event()
        self._watchdog_stop = stop_event
        weblink_proc = self._weblink_proc

        def watchdog():
            import time
            # Simply wait for the duration, checking if Chromium exited early
            elapsed = 0.0
            step = 0.5
            while elapsed < duration and not stop_event.is_set():
                if weblink_proc is not None and weblink_proc.poll() is not None:
                    Clock.schedule_once(self.next_media, 0)
                    return
                time.sleep(step)
                elapsed += step
            if not stop_event.is_set():
                Clock.schedule_once(self.next_media, 0)

        self._weblink_watchdog_thread = threading.Thread(
            target=watchdog, daemon=True, name='weblink-watchdog-win'
        )
        self._weblink_watchdog_thread.start()

    signage_main.SignagePlayer._start_inactivity_watchdog = _windows_watchdog

    # Patch the _kill_weblink_after_frame to work without Linux-specific code
    def _windows_kill_weblink_after_frame(self):
        from kivy.clock import Clock
        self._stop_inactivity_watchdog()
        self._kill_weblink_preload()
        proc = self._weblink_proc
        self._weblink_proc = None
        if proc is not None and proc.poll() is None:
            def _do_kill(dt):
                if proc.poll() is None:
                    try:
                        proc.terminate()
                        try:
                            proc.wait(timeout=3)
                        except Exception:
                            proc.kill()
                    except Exception:
                        pass
            Clock.schedule_once(_do_kill, 0)
    signage_main.SignagePlayer._kill_weblink_after_frame = _windows_kill_weblink_after_frame

    # Patch _prewarm_weblink for Windows
    def _windows_prewarm_weblink(self, url):
        from urllib.parse import urlparse
        if not url:
            return
        scheme = urlparse(url).scheme.lower()
        if scheme not in ('http', 'https'):
            return
        browser = _windows_find_browser()
        if not browser:
            return
        import subprocess
        from kivy.logger import Logger
        self._kill_weblink_preload()
        try:
            Logger.debug(f"SignagePlayer: Pre-warming weblink off-screen: {url}")
            self._weblink_preload_proc = subprocess.Popen([
                browser,
                '--incognito',
                '--no-first-run',
                '--window-position=-9999,-9999',
                url,
            ], shell=False)
        except Exception as exc:
            Logger.debug(f"SignagePlayer: Pre-warm failed (non-fatal): {exc}")
            self._weblink_preload_proc = None
    signage_main.SignagePlayer._prewarm_weblink = _windows_prewarm_weblink

    # Patch cleanup of temp auth file (was using /tmp/)
    _original_connection_test = signage_main.SettingsPopup.test_connection

    def _windows_test_connection(self):
        """Test connection with Windows-safe temp file path."""
        import tempfile as _tf
        import os as _os
        from player_auth import PlayerAuth
        import re
        import threading
        from kivy.clock import Clock

        self.ids.connection_status.text = 'Testing connection...'
        self.ids.connection_status.color = (1, 0.7, 0, 1)

        def run_test():
            try:
                server_ip = self.ids.server_input.text.strip()
                screen_name = self.ids.screen_input.text.strip()
                quickconnect = self.ids.quickconnect_input.text.strip()
                port = self.ids.port_input.text.strip() or self.player.config.get('port', '')
                use_https = self.player.config.get('use_https', True)
                verify_ssl = self.player.config.get('verify_ssl', True)

                if not all([server_ip, screen_name, quickconnect]):
                    Clock.schedule_once(lambda dt: self.update_connection_status('Error: Fill all fields', False))
                    return

                if server_ip.startswith('http://') or server_ip.startswith('https://'):
                    server_url = server_ip
                    if ':' not in server_ip.replace('https://', '').replace('http://', ''):
                        if port and port not in ('443', '80'):
                            server_url = f"{server_ip}:{port}"
                else:
                    protocol = "https" if use_https else "http"
                    if ':' in server_ip:
                        server_url = f"{protocol}://{server_ip}"
                    else:
                        server_url = f"{protocol}://{server_ip}:{port}" if port else f"{protocol}://{server_ip}"

                # Use Windows temp path
                temp_file = _os.path.join(_tf.gettempdir(), 'temp_auth_test.json')
                auth = PlayerAuth(temp_file, use_https=use_https, verify_ssl=verify_ssl)
                success, error = auth.authenticate(server_url=server_url, hostname=screen_name, quickconnect_code=quickconnect)

                try:
                    if _os.path.exists(temp_file):
                        _os.remove(temp_file)
                except Exception:
                    pass

                if success:
                    player_name = auth.get_player_name()
                    Clock.schedule_once(lambda dt: self.update_connection_status(f'✓ Connected: {player_name}', True))
                else:
                    Clock.schedule_once(lambda dt: self.update_connection_status(f'✗ Failed: {error}', False))

            except Exception as e:
                Clock.schedule_once(lambda dt: self.update_connection_status(f'✗ Error: {str(e)}', False))

        threading.Thread(target=run_test, daemon=True).start()

    signage_main.SettingsPopup.test_connection = _windows_test_connection

    return signage_main


# =====================================================================
# 4. Adjust path so we can import the src modules
# =====================================================================
# When run from PyInstaller .exe: the runtime hook inserts paths.
# When run as plain python, we add src/ relative to this file.
_script_dir = Path(__file__).resolve().parent
_project_root = _script_dir.parent
_src_dir = _project_root / 'src'

for p in [str(_src_dir), str(_project_root)]:
    if p not in sys.path:
        sys.path.insert(0, p)

# =====================================================================
# 5. Determine the local data directory (next to the executable)
# =====================================================================
# The pyi_runtime_hook.py (when packaged) sets KIWY_DATA_DIR.
# When running in dev mode from python, use the project root.
# The executable will create its own local folders for playlist,
# media, config, and logs where the executable is launched.
DATA_DIR = os.environ.get('KIWY_DATA_DIR', str(_project_root))

# Create local data folders NEXT TO the executable
os.makedirs(os.path.join(DATA_DIR, 'config', 'resources'), exist_ok=True)
os.makedirs(os.path.join(DATA_DIR, 'media'), exist_ok=True)
os.makedirs(os.path.join(DATA_DIR, 'media', 'edited_media'), exist_ok=True)
os.makedirs(os.path.join(DATA_DIR, 'playlists'), exist_ok=True)
os.makedirs(os.path.join(DATA_DIR, 'logs'), exist_ok=True)
os.makedirs(os.path.join(DATA_DIR, 'config', 'certs'), exist_ok=True)

# =====================================================================
# 6. Set Kivy config BEFORE importing Kivy
# =====================================================================
os.environ['KIVY_NO_FILELOG'] = '1'  # Avoid file logging issues on Windows
os.environ['KIVY_HOME'] = os.path.join(DATA_DIR, '.kivy')

from kivy.config import Config
Config.set('kivy', 'keyboard_mode', '')  # Disable default virtual keyboard
Config.set('graphics', 'fullscreen', '0')
Config.set('graphics', 'window_state', 'maximized')
Config.set('graphics', 'multisampling', '0')
Config.set('graphics', 'fast_rgba', '1')
Config.set('kivy', 'log_level', 'warning')

# =====================================================================
# 7. Patch the main module, then run the app
# =====================================================================
if __name__ == '__main__':
    try:
        # Write a startup marker so we know the .exe at least launched
        try:
            os.makedirs(os.path.join(DATA_DIR, 'logs'), exist_ok=True)
            marker = os.path.join(DATA_DIR, 'logs', 'startup_marker.txt')
            with open(marker, 'w') as f:
                f.write(f"run_win.py started at {__import__('time').time()}\n")
        except Exception:
            pass

        # Apply all Windows patches before launching
        try:
            patched_main = _patch_main()
        except Exception as e:
            # Catch early import errors (pre-Logger) to a file
            import traceback
            try:
                err_log = os.path.join(DATA_DIR, 'logs', 'startup_error.log')
                with open(err_log, 'w') as f:
                    f.write(f"Error in _patch_main(): {e}\n")
                    traceback.print_exc(file=f)
            except Exception:
                pass
            raise  # Re-raise so console shows it too

        from kivy.logger import Logger
        Logger.info("=" * 80)
        Logger.info("Kiwy Signage Player - Windows Edition")
        Logger.info(f"Python: {sys.version}")
        Logger.info(f"Platform: {platform.platform()}")
        Logger.info(f"Data directory: {DATA_DIR}")
        Logger.info("=" * 80)

        # Patch base_dir in SignagePlayer instances to point to local data folder
        _original_init = patched_main.SignagePlayer.__init__

        def _patched_init(self, **kwargs):
            """Override SignagePlayer.__init__ to use local data folders.
            
            Creates all necessary folders (config, media, playlists, logs)
            in the same directory where the executable is launched.
            """
            # Call parent Widget.__init__
            _original_init(self, **kwargs)
            # Now override ALL paths to point to local data directory
            # (where the .exe is located)
            self.base_dir = DATA_DIR
            self.config_dir = os.path.join(DATA_DIR, 'config')
            self.media_dir = os.path.join(DATA_DIR, 'media')
            self.playlists_dir = os.path.join(DATA_DIR, 'playlists')
            self.config_file = os.path.join(self.config_dir, 'app_config.json')
            self.resources_path = os.path.join(self.config_dir, 'resources')
            self.heartbeat_file = os.path.join(DATA_DIR, '.player_heartbeat')
            # Ensure all required folders exist locally
            for directory in [
                self.config_dir,
                self.resources_path,
                os.path.join(self.media_dir, 'edited_media'),
                self.playlists_dir,
                os.path.join(DATA_DIR, 'logs'),
                os.path.join(DATA_DIR, 'config', 'certs'),
            ]:
                os.makedirs(directory, exist_ok=True)

        patched_main.SignagePlayer.__init__ = _patched_init

        # Patch SSLManager cert directory to use local data folder
        # ssl_utils is imported by player_auth.py and get_playlists_v2.py, not main.py
        import ssl_utils
        ssl_utils.SSLManager.CERT_DIR = os.path.join(DATA_DIR, 'config', 'certs')
        ssl_utils.SSLManager.CERT_FILE = os.path.join(
            ssl_utils.SSLManager.CERT_DIR, 'server_cert.pem'
        )
        ssl_utils.SSLManager.CERT_INFO_FILE = os.path.join(
            ssl_utils.SSLManager.CERT_DIR, 'cert_info.json'
        )

        # Run the app
        try:
            app = patched_main.SignagePlayerApp()
            app.run()
        except KeyboardInterrupt:
            Logger.info("Application stopped by user (Ctrl+C)")
        except SystemExit as _se:
            Logger.critical(f"Kivy SystemExit (likely window provider missing): {_se}")
            try:
                crash_log = os.path.join(DATA_DIR, 'logs', 'crash.log')
                with open(crash_log, 'w') as f:
                    f.write(f"Kivy SystemExit: {_se}\n")
                    f.write("This usually means Kivy could not find a window provider on this system.\n")
            except Exception:
                pass
            _show_error_box(
                "Kiwy Signage Player - Kivy Error",
                f"Kivy exited: {_se}\n\n"
                "This usually means Kivy could not create a window.\n"
                "Check your GPU drivers and DirectX installation.\n\n"
                "See logs/crash.log for details."
            )
            sys.exit(1)
        except Exception as e:
            Logger.critical(f"Fatal error: {e}")
            Logger.exception("Full traceback:")
            # Also write to a crash log next to the executable
            try:
                import traceback
                crash_log = os.path.join(DATA_DIR, 'logs', 'crash.log')
                with open(crash_log, 'w') as f:
                    f.write(f"Fatal error: {e}\n")
                    traceback.print_exc(file=f)
            except Exception:
                pass
            sys.exit(1)
        finally:
            Logger.info("Application shutdown complete")
    except BaseException as _top_e:
        # Catch any error BEFORE Logger is available (including SystemExit)
        import traceback as _tb
        _trace = _tb.format_exc()
        try:
            _crash_log = os.path.join(DATA_DIR, 'logs', 'fatal_crash.log')
            with open(_crash_log, 'w') as _f:
                _f.write(f"FATAL (pre-Logger): {_top_e}\n")
                _f.write(_trace)
        except Exception:
            pass
        _show_error_box(
            "Kiwy Signage Player - Startup Error",
            f"{_top_e}\n\nSee logs/fatal_crash.log for details."
        )
        raise  # Re-raise so .exe still shows the error
