#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TSID QR Display - GUI QR code display for GDM login screen
Requests QR auth from TSID server and displays it as a graphical window.
"""

import subprocess
import sys
import os
import json
import urllib.request
import urllib.error
import time
import threading
import tempfile
import glob
print("[TSID] Python script started", file=sys.stderr)
sys.stderr.flush()

try:
    import gi
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk, Gdk, GLib, GdkPixbuf
    print("[TSID] GTK modules imported successfully", file=sys.stderr)
    sys.stderr.flush()
except ImportError as e:
    print(f"[TSID] Import error: {e}", file=sys.stderr)
    print("[TSID] Please install: python3-gi python3-gi-cairo", file=sys.stderr)
    sys.exit(1)


def get_dm_service():
    """Get the display manager service from /etc/tsid/os-config.
    
    Config file is created at install time with:
    TSID_DISPLAY_MANAGER=gdm or lightdm
    """
    config_file = "/etc/tsid/os-config"
    
    # 1. Read from config file (set at install time)
    try:
        with open(config_file, "r") as f:
            for line in f:
                if line.startswith("TSID_DISPLAY_MANAGER="):
                    dm = line.split("=", 1)[1].strip()
                    print(f"[TSID] Read DM from config: {dm}", file=sys.stderr)
                    return dm
    except Exception as e:
        print(f"[TSID] Error reading {config_file}: {e}", file=sys.stderr)
    
    # 2. Fallback: detect by OS version (for emergency)
    if os.path.exists("/etc/rocky-release"):
        try:
            with open("/etc/rocky-release", "r") as f:
                content = f.read()
                import re
                match = re.search(r'(\d+)', content)
                if match:
                    version = int(match.group(1))
                    if version >= 10:
                        print("[TSID] Fallback: Rocky 10 detected - using GDM", file=sys.stderr)
                        return "gdm"
                    else:
                        print(f"[TSID] Fallback: Rocky {version} detected - using LightDM", file=sys.stderr)
                        return "lightdm"
        except Exception as e:
            print(f"[TSID] Error reading Rocky version: {e}", file=sys.stderr)
    
    if os.path.exists("/etc/debian_version"):
        print("[TSID] Fallback: Debian/Ubuntu detected - using LightDM", file=sys.stderr)
        return "lightdm"
    
    # 3. Default: LightDM
    print("[TSID] No config found, defaulting to LightDM", file=sys.stderr)
    return "lightdm"


def create_temp_user_and_autologin(user_code, role):
    """Create temporary user and configure LightDM autologin using setuid helper."""
    user_mapping_file = "/etc/tsid/user_mapping"
    
    # Check if user already exists in mapping (4필드: PIN:user_code:account:role)
    imsi_user = None
    if os.path.exists(user_mapping_file):
        try:
            with open(user_mapping_file, "r") as f:
                for line in f:
                    parts = line.strip().split(":")
                    # 4필드: PIN:user_code:account:role → user_code(2번째)로 검색
                    if len(parts) >= 4 and parts[1] == user_code:
                        imsi_user = parts[2]
                        print(f"[TSID] Found existing mapping: {user_code} -> {imsi_user}", file=sys.stderr)
                        sys.stderr.flush()
                        break
        except Exception as e:
            print(f"[TSID] Error reading user_mapping: {e}", file=sys.stderr)
            sys.stderr.flush()
    
    # If not found, find next available imsi-user number
    if not imsi_user:
        next_num = 1
        if os.path.exists(user_mapping_file):
            try:
                with open(user_mapping_file, "r") as f:
                    for line in f:
                        parts = line.strip().split(":")
                        # 4필드에서 account(3번째)에서 imsi-user 번호 추출
                        if len(parts) >= 4 and parts[2].startswith("imsi-user"):
                            try:
                                num = int(parts[2].replace("imsi-user", ""))
                                if num >= next_num:
                                    next_num = num + 1
                            except ValueError:
                                pass
            except Exception:
                pass
        
        imsi_user = f"imsi-user{next_num}"
    
    # Use setuid helper to configure autologin
    # This helper runs as root and handles user creation, mapping, and LightDM config + restart
    print(f"[TSID] Calling autologin helper for {imsi_user} (role={role})", file=sys.stderr)
    sys.stderr.flush()
    try:
        result = subprocess.run(
            ["/usr/bin/tsid-autologin-helper", user_code, imsi_user, role],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            timeout=30,
            universal_newlines=True
        )
        
        if result.returncode == 0:
            print(f"[TSID] Autologin helper succeeded", file=sys.stderr)
            sys.stderr.flush()
            if result.stderr:
                print(f"[TSID] Helper output: {result.stderr}", file=sys.stderr)
                sys.stderr.flush()
            return imsi_user
        else:
            print(f"[TSID] Autologin helper failed: returncode={result.returncode}", file=sys.stderr)
            sys.stderr.flush()
            if result.stderr:
                print(f"[TSID] stderr: {result.stderr}", file=sys.stderr)
                sys.stderr.flush()
            return None
    except subprocess.TimeoutExpired:
        print(f"[TSID] Autologin helper timed out", file=sys.stderr)
        sys.stderr.flush()
        return None
    except Exception as e:
        print(f"[TSID] Error calling autologin helper: {e}", file=sys.stderr)
        sys.stderr.flush()
        return None




def get_server_code():
    """Read server code from config file."""
    config_file = "/etc/tsid/config.conf"
    server_code_file = "/etc/tsid/server_code"
    company_code_file = "/etc/tsid/company_code"

    # First try server_code file (correct location)
    if os.path.exists(server_code_file):
        with open(server_code_file, "r") as f:
            code = f.read().strip()
            if code:
                print(f"[TSID] Using server code from {server_code_file}: {code}", file=sys.stderr)
                return code

    # Then try company_code file (fallback - for backward compatibility)
    if os.path.exists(company_code_file):
        with open(company_code_file, "r") as f:
            code = f.read().strip()
            if code:
                print(f"[TSID] Using company code from {company_code_file}: {code}", file=sys.stderr)
                return code

    # Finally try config file
    if os.path.exists(config_file):
        with open(config_file, "r") as f:
            for line in f:
                line = line.strip()
                if line.startswith("server_code="):
                    code = line.split("=", 1)[1].strip()
                    print(f"[TSID] Using server code from {config_file}: {code}", file=sys.stderr)
                    return code
                if line.startswith("SERVER_CODE="):
                    code = line.split("=", 1)[1].strip()
                    print(f"[TSID] Using server code from {config_file}: {code}", file=sys.stderr)
                    return code
    
    print("[TSID] No server code found in any location", file=sys.stderr)
    print(f"[TSID] Checked: {company_code_file}, {server_code_file}, {config_file}", file=sys.stderr)
    return None


def find_display():
    """Find the active X display and Xauthority.
    When running as gdm user, use gdm's own Xauthority directly.
    """
    import pwd

    # Get current user info
    try:
        current_uid = os.getuid()
        current_user = pwd.getpwuid(current_uid).pw_name
    except Exception:
        current_user = "root"
        current_uid = 0

    # If already running as gdm, use gdm's own Xauthority paths
    gdm_xauth_patterns = []
    if current_user == "gdm" or current_uid != 0:
        gdm_xauth_patterns = [
            f"/run/user/{current_uid}/gdm/Xauthority",
            os.path.expanduser("~/.Xauthority"),
            f"/var/lib/gdm3/.Xauthority",
        ]
    else:
        # Running as root - find gdm's Xauthority
        try:
            gdm_uid = pwd.getpwnam("gdm").pw_uid
        except Exception:
            gdm_uid = 126
        gdm_xauth_patterns = [
            f"/run/user/{gdm_uid}/gdm/Xauthority",
            "/var/lib/gdm3/.Xauthority",
            "/var/run/gdm3/auth-for-gdm*/database",
            "/run/gdm3/auth-for-gdm*/database",
        ]

    for _ in range(60):
        # Find X display from lock files
        locks = glob.glob("/tmp/.X*-lock")
        for lock in sorted(locks):
            try:
                display_num = lock.replace("/tmp/.X", "").replace("-lock", "").strip()
                if not display_num.isdigit():
                    continue
                display = f":{display_num}"

                # Try gdm-specific Xauthority paths first
                for pattern in gdm_xauth_patterns:
                    matches = glob.glob(pattern)
                    if matches:
                        xauth = matches[0]
                        if os.path.exists(xauth):
                            print(f"[TSID] Using DISPLAY={display} XAUTHORITY={xauth}", file=sys.stderr)
                            return display, xauth

                # Fallback: return display without xauth (gdm user may not need it)
                if current_user == "gdm":
                    print(f"[TSID] Using DISPLAY={display} (no xauth, gdm user)", file=sys.stderr)
                    return display, None
            except Exception:
                continue
        time.sleep(1)
    return ":0", None


def request_qr(server_code):
    """Request QR URL and state_code from TSID server."""
    url = "https://tpg.tsidcert.com/svr/auth/qr"
    data = json.dumps({"server_code": server_code}).encode("utf-8")
    req = urllib.request.Request(
        url,
        data=data,
        headers={"Content-Type": "application/json"},
        method="POST"
    )
    try:
        with urllib.request.urlopen(req, timeout=10) as resp:
            body = resp.read().decode("utf-8")
            obj = json.loads(body)
            return obj.get("qr_url"), obj.get("state_code")
    except Exception as e:
        print(f"[TSID] QR request failed: {e}", file=sys.stderr)
        return None, None


def poll_auth(server_code, state_code):
    """Poll TSID server for authentication result. Returns (name, role, user_code) or None."""
    url = "https://tpg.tsidcert.com/svr/auth/check"
    data = json.dumps({"server_code": server_code, "state_code": state_code}).encode("utf-8")
    req = urllib.request.Request(
        url,
        data=data,
        headers={"Content-Type": "application/json"},
        method="POST"
    )
    try:
        with urllib.request.urlopen(req, timeout=10) as resp:
            body = resp.read().decode("utf-8")
            obj = json.loads(body)
            status = obj.get("status")
            print(f"[TSID] Poll response: status={status} body={body[:200]}", file=sys.stderr)
            if status == "C":  # C=인증 성공
                return obj.get("name"), obj.get("role"), obj.get("user_code")
            elif status == "B":  # B=등록된 사용자 아님
                return False, None, None
            elif status == "R":  # R=인증 거절
                return False, None, None
            return None, None, None  # P=진행중
    except Exception as e:
        print(f"[TSID] Poll failed: {e}", file=sys.stderr)
        return None, None, None


def generate_qr_image(qr_url, output_path):
    """Download QR image from URL or generate using qrencode if URL is not an image."""
    # If qr_url looks like an image URL, download it directly
    if qr_url.startswith("http") and any(qr_url.lower().endswith(ext) for ext in [".png", ".jpg", ".jpeg", ".gif", ".bmp"]):
        try:
            urllib.request.urlretrieve(qr_url, output_path)
            print(f"[TSID] QR image downloaded from: {qr_url}", file=sys.stderr)
            return True
        except Exception as e:
            print(f"[TSID] QR image download failed: {e}", file=sys.stderr)
    
    # If qr_url starts with http but no image extension, try downloading anyway
    if qr_url.startswith("http"):
        try:
            urllib.request.urlretrieve(qr_url, output_path)
            print(f"[TSID] QR image downloaded from: {qr_url}", file=sys.stderr)
            return True
        except Exception as e:
            print(f"[TSID] QR image download failed: {e}, falling back to qrencode", file=sys.stderr)
    
    # Fallback: generate QR code from the URL string using qrencode
    try:
        subprocess.run(
            ["qrencode", "-o", output_path, "-s", "8", "-m", "2", qr_url],
            check=True, timeout=10
        )
        return True
    except Exception as e:
        print(f"[TSID] qrencode failed: {e}", file=sys.stderr)
        return False


def run_gui(qr_url, state_code, server_code):
    """Display QR code in a fullscreen GTK window and poll for auth."""
    # Wayland 세션에서는 X display 검색이 불필요
    if "WAYLAND_DISPLAY" in os.environ:
        print(f"[TSID] Running in Wayland session: WAYLAND_DISPLAY={os.environ['WAYLAND_DISPLAY']}", file=sys.stderr)
    elif "DISPLAY" in os.environ and "XAUTHORITY" in os.environ:
        display = os.environ["DISPLAY"]
        xauth = os.environ["XAUTHORITY"]
        print(f"[TSID] Using existing DISPLAY={display} XAUTHORITY={xauth}", file=sys.stderr)
    else:
        display, xauth = find_display()
        os.environ["DISPLAY"] = display
        if xauth:
            os.environ["XAUTHORITY"] = xauth
        print(f"[TSID] Auto-detected DISPLAY={display} XAUTHORITY={xauth}", file=sys.stderr)

    # Download or generate QR image
    qr_image_path = tempfile.mktemp(suffix=".png")
    if not generate_qr_image(qr_url, qr_image_path):
        print("[TSID] Failed to generate QR image", file=sys.stderr)
        return None

    # 화면 크기 감지 (Wayland/X11 모두 지원)
    screen_width = 1920
    screen_height = 1080
    display = Gdk.Display.get_default()
    if display:
        monitor = display.get_primary_monitor() or display.get_monitor(0)
        if monitor:
            geom = monitor.get_geometry()
            screen_width = geom.width
            screen_height = geom.height
            print(f"[TSID] Screen size: {screen_width}x{screen_height}", file=sys.stderr)
        else:
            print(f"[TSID] No monitor detected, using default {screen_width}x{screen_height}", file=sys.stderr)
    else:
        # display가 None이면 GTK 초기화 재시도
        print("[TSID] No display found, attempting Gtk.init()", file=sys.stderr)
        Gtk.init([])
        display = Gdk.Display.get_default()
        if display:
            monitor = display.get_primary_monitor() or display.get_monitor(0)
            if monitor:
                geom = monitor.get_geometry()
                screen_width = geom.width
                screen_height = geom.height

    win = Gtk.Window()
    win.set_title("TSID Authentication")
    win.set_decorated(False)
    win.set_keep_above(True)
    win.set_skip_taskbar_hint(True)
    win.set_skip_pager_hint(True)
    win.set_type_hint(Gdk.WindowTypeHint.SPLASHSCREEN)
    win.set_default_size(screen_width, screen_height)
    win.fullscreen()
    win.override_background_color(
        Gtk.StateFlags.NORMAL,
        Gdk.RGBA(0.27, 0.13, 0.38, 1.0)
    )

    vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=20)
    vbox.set_halign(Gtk.Align.CENTER)
    vbox.set_valign(Gtk.Align.CENTER)
    win.add(vbox)

    # Title label
    title = Gtk.Label()
    title.set_markup(
        '<span foreground="white" font="24" weight="bold">TSID Authentication</span>'
    )
    vbox.pack_start(title, False, False, 0)

    # Instruction label
    inst = Gtk.Label()
    inst.set_markup(
        '<span foreground="white" font="14">Scan QR code with TSID mobile app</span>'
    )
    vbox.pack_start(inst, False, False, 0)

    # QR image
    try:
        pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(qr_image_path, 300, 300, True)
        img = Gtk.Image.new_from_pixbuf(pixbuf)
        vbox.pack_start(img, False, False, 10)
    except Exception as e:
        print(f"[TSID] Image load failed: {e}", file=sys.stderr)

    # Countdown label
    countdown_label = Gtk.Label()
    countdown_label.set_markup(
        '<span foreground="white" font="14" weight="bold">3:00</span>'
    )
    vbox.pack_start(countdown_label, False, False, 0)

    # Status label
    status_label = Gtk.Label()
    status_label.set_markup(
        '<span foreground="white" font="12">Waiting for authentication...</span>'
    )
    vbox.pack_start(status_label, False, False, 0)

    win.show_all()
    win.present()

    auth_result = {"done": False, "user": None}
    start_time = time.time()
    countdown_deadline = start_time + 180  # 3 minutes

    def update_countdown():
        elapsed = time.time() - start_time
        remaining = max(0, countdown_deadline - time.time())
        
        if remaining > 0:
            minutes = int(remaining // 60)
            seconds = int(remaining % 60)
            countdown_text = f"{minutes}:{seconds:02d}"
            GLib.idle_add(countdown_label.set_markup,
                f'<span foreground="white" font="14" weight="bold">{countdown_text}</span>')
            return True  # Continue timeout
        else:
            GLib.idle_add(countdown_label.set_markup,
                '<span foreground="red" font="14" weight="bold">Time expired!</span>')
            GLib.idle_add(status_label.set_markup,
                '<span foreground="yellow" font="12">Refreshing QR...</span>')
            # Close window to trigger QR refresh
            time.sleep(2)
            GLib.idle_add(Gtk.main_quit)
            return False  # Stop timeout

    def poll_thread():
        deadline = time.time() + 180  # match QR display timeout
        poll_count = 0
        while time.time() < deadline and not auth_result["done"]:
            poll_count += 1
            print(f"[TSID] Polling #{poll_count} state_code={state_code[:8]}...", file=sys.stderr)
            name, role, user_code = poll_auth(server_code, state_code)
            if name is False:
                # Rejected
                GLib.idle_add(status_label.set_markup,
                    '<span foreground="red" font="12">Authentication rejected</span>')
                time.sleep(2)
                GLib.idle_add(Gtk.main_quit)
                return
            elif name:
                # Success
                GLib.idle_add(status_label.set_markup,
                    '<span foreground="lightgreen" font="12">Auth success! Logging in...</span>')
                auth_result["done"] = True
                auth_result["name"] = name
                auth_result["role"] = role
                auth_result["user_code"] = user_code

                # Create temporary user and setup autologin
                print(f"[TSID] Creating temporary user for {user_code}", file=sys.stderr)
                sys.stderr.flush()
                imsi_user = create_temp_user_and_autologin(user_code, role)
                if imsi_user:
                    print(f"[TSID] Autologin configured for {imsi_user}", file=sys.stderr)
                    sys.stderr.flush()
                    
                    # DM restart to apply autologin config
                    dm_service = get_dm_service()
                    dm_name = "GDM" if dm_service == "gdm" else "LightDM"
                    print(f"[TSID] Restarting {dm_name} to apply new autologin config", file=sys.stderr)
                    sys.stderr.flush()
                    try:
                        subprocess.run(['sudo', '/usr/bin/systemctl', 'restart', dm_service],
                                       timeout=5, check=False)
                    except Exception as e:
                        print(f"[TSID] DM restart failed: {e}", file=sys.stderr)
                    time.sleep(1)
                    os._exit(0)
                else:
                    print("[TSID] Failed to configure autologin", file=sys.stderr)
                    sys.stderr.flush()
                    time.sleep(1)
                    os._exit(1)
            time.sleep(3)

        # Timeout
        GLib.idle_add(status_label.set_markup,
            '<span foreground="yellow" font="12">Timeout. Refreshing QR...</span>')
        time.sleep(2)
        GLib.idle_add(Gtk.main_quit)

    # Start countdown timer
    GLib.timeout_add_seconds(1, update_countdown)
    
    t = threading.Thread(target=poll_thread, daemon=True)
    t.start()

    Gtk.main()

    # Cleanup
    try:
        os.unlink(qr_image_path)
    except Exception:
        pass

    return auth_result.get("name")




def main():
    """Main function - LightDM greeter mode only."""
    print("[TSID] main() function called", file=sys.stderr)
    sys.stderr.flush()
    
    server_code = get_server_code()
    if not server_code:
        print("[TSID] Server code not found", file=sys.stderr)
        sys.exit(1)
    
    print(f"[TSID] Server code loaded, entering main loop", file=sys.stderr)
    sys.stderr.flush()
    
    while True:
        print("[TSID] Requesting QR code from server...", file=sys.stderr)
        sys.stderr.flush()
        qr_url, state_code = request_qr(server_code)
        if not qr_url or not state_code:
            print("[TSID] Failed to get QR URL. Retrying in 5s...", file=sys.stderr)
            time.sleep(5)
            continue
        
        result = run_gui(qr_url, state_code, server_code)
        if result:
            print("[TSID] Auth success", file=sys.stderr)
            sys.stderr.flush()
            # LightDM restart is handled by autologin-helper (setuid root)
            # Greeter just exits, helper already scheduled the restart
            return 0
        print("[TSID] QR timeout, refreshing...", file=sys.stderr)
    
    return 0


if __name__ == "__main__":
    main()
