package player

import (
	"fmt"
	"log"
	"os"
	"os/user"
	"path/filepath"
	"sort"
	"strings"
)

// display beschreibt, wohin mpv sein Bild malen soll.
type display struct {
	mode string   // "drm", "wayland" oder "x11"
	env  []string // zusaetzliche Umgebungsvariablen fuer mpv
	note string   // Hinweis fuers Protokoll, wenn etwas nicht stimmt
}

// resolveDisplay ermittelt die Bildausgabe.
//
// DISPLAY_MODE="auto" (Standard) sucht zuerst nach einer laufenden
// Desktop-Sitzung des eigenen Benutzers und faellt sonst auf die direkte
// Ausgabe ueber DRM/KMS zurueck. "drm" und "desktop" erzwingen die jeweilige
// Variante.
//
// Bei "desktop" ohne erreichbare Sitzung wird ein Fehler geliefert statt auf
// DRM auszuweichen: direkt nach dem Einschalten steht die Sitzung oft noch
// nicht, und ein Ausweichen wuerde dem gerade startenden Desktop den
// Bildschirm wegnehmen. Der Player versucht es dann einfach spaeter erneut.
func resolveDisplay(mode string) (display, error) {
	switch mode {
	case "drm":
		d := display{mode: "drm"}
		if other := sessionHint(); other != "" {
			d.note = "DISPLAY_MODE=drm ist gesetzt, aber es laeuft ein Desktop. " +
				"mpv kann den Bildschirm so nicht bekommen. " + other
		}
		return d, nil

	case "desktop":
		if d := detectSession(); d.mode != "" {
			return d, nil
		}
		msg := "keine erreichbare Desktop-Sitzung gefunden (DISPLAY_MODE=desktop)"
		if hint := sessionHint(); hint != "" {
			msg += ". " + hint
		}
		return display{}, fmt.Errorf("%s", msg)

	default: // "auto" und alles Unbekannte
		if d := detectSession(); d.mode != "" {
			return d, nil
		}
		return display{mode: "drm", note: sessionHint()}, nil
	}
}

// detectSession sucht die Wayland- bzw. X11-Sitzung des eigenen Benutzers.
func detectSession() display {
	runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
	if runtimeDir == "" {
		runtimeDir = fmt.Sprintf("/run/user/%d", os.Getuid())
	}
	var env []string
	if os.Getenv("XDG_RUNTIME_DIR") == "" {
		env = append(env, "XDG_RUNTIME_DIR="+runtimeDir)
	}

	// Wayland: labwc, wayfire & Co. legen den Socket im Laufzeitverzeichnis ab.
	// Je nach System heisst er wayland-0, wayland-1 - deshalb wird gesucht
	// statt geraten.
	if name := os.Getenv("WAYLAND_DISPLAY"); name != "" && socketExists(runtimeDir, name) {
		return display{mode: "wayland", env: env}
	}
	if name := findWaylandSocket(runtimeDir); name != "" {
		return display{mode: "wayland", env: append(env, "WAYLAND_DISPLAY="+name)}
	}

	// X11
	if d := os.Getenv("DISPLAY"); d != "" {
		return display{mode: "x11", env: env}
	}
	if names, _ := filepath.Glob("/tmp/.X11-unix/X*"); len(names) > 0 {
		sort.Strings(names)
		num := strings.TrimPrefix(filepath.Base(names[0]), "X")
		return display{mode: "x11", env: append(env, "DISPLAY=:"+num)}
	}

	return display{}
}

func socketExists(dir, name string) bool {
	if filepath.IsAbs(name) {
		_, err := os.Stat(name)
		return err == nil
	}
	_, err := os.Stat(filepath.Join(dir, name))
	return err == nil
}

func findWaylandSocket(dir string) string {
	matches, err := filepath.Glob(filepath.Join(dir, "wayland-*"))
	if err != nil {
		return ""
	}
	sort.Strings(matches)
	for _, m := range matches {
		if strings.HasSuffix(m, ".lock") {
			continue
		}
		if info, err := os.Stat(m); err == nil && info.Mode()&os.ModeSocket != 0 {
			return filepath.Base(m)
		}
	}
	return ""
}

// sessionHint schaut nach, ob ein ANDERER Benutzer einen Desktop laufen hat.
// Das ist der haeufigste Stolperstein: der Dienst laeuft als "tvwall", der
// Desktop gehoert aber "pi" - und auf dessen Sitzung kommt niemand sonst.
func sessionHint() string {
	me := os.Getuid()
	dirs, err := os.ReadDir("/run/user")
	if err != nil {
		return ""
	}
	for _, d := range dirs {
		if !d.IsDir() || d.Name() == fmt.Sprint(me) {
			continue
		}
		sockets, _ := filepath.Glob(filepath.Join("/run/user", d.Name(), "wayland-*"))
		if len(sockets) == 0 {
			continue
		}
		name := d.Name()
		if u, err := user.LookupId(d.Name()); err == nil {
			name = u.Username
		}
		return fmt.Sprintf(
			"Es laeuft eine Desktop-Sitzung des Benutzers %q, auf die dieser Dienst "+
				"keinen Zugriff hat. In /etc/tvwall/tvwall.conf setzen: "+
				"RUN_USER=%q und DISPLAY_MODE=\"desktop\", danach "+
				"'sudo /opt/tvwall/install.sh --no-ap'.", name, name)
	}
	return ""
}

// logDisplay schreibt die getroffene Wahl ins Protokoll.
func (d display) logDisplay() {
	switch d.mode {
	case "wayland":
		log.Printf("player: Bildausgabe ueber Wayland (%s)", envValue(d.env, "WAYLAND_DISPLAY", os.Getenv("WAYLAND_DISPLAY")))
	case "x11":
		log.Printf("player: Bildausgabe ueber X11 (%s)", envValue(d.env, "DISPLAY", os.Getenv("DISPLAY")))
	default:
		log.Printf("player: Bildausgabe direkt ueber DRM/KMS (ohne Desktop)")
	}
	if d.note != "" {
		log.Printf("player: HINWEIS: %s", d.note)
	}
}

func envValue(env []string, key, fallback string) string {
	for _, e := range env {
		if strings.HasPrefix(e, key+"=") {
			return strings.TrimPrefix(e, key+"=")
		}
	}
	return fallback
}
