package playerserver

import (
	"context"
	"embed"
	"encoding/json"
	"fmt"
	"io/fs"
	"net"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

//go:embed assets
var assetsFS embed.FS

// NowPlaying describes what the player should currently display.
type NowPlaying struct {
	URL          string `json:"url,omitempty"`
	Status       string `json:"status"`
	Connectivity string `json:"connectivity"`
}

// InfoItem is a single entry shown in the lower-third sysinfo overlay.
// Add new items in collectSysInfo to extend the overlay.
type InfoItem struct {
	Label string `json:"label"`
	Value string `json:"value"`
}

// SysInfo holds the items shown in the lower-third overlay.
type SysInfo struct {
	Items []InfoItem `json:"items"`
}

// Server serves the local player UI to Chromium.
type Server struct {
	listenAddr string
	nowFn      func() NowPlaying
}

// New creates a Server. listenAddr is e.g. "127.0.0.1:8090".
// nowFn is called on each request and returns the current playback state.
func New(listenAddr string, nowFn func() NowPlaying) *Server {
	return &Server{listenAddr: listenAddr, nowFn: nowFn}
}

// Run starts the HTTP server and blocks until ctx is cancelled.
func (s *Server) Run(ctx context.Context) error {
	sub, err := fs.Sub(assetsFS, "assets")
	if err != nil {
		return err
	}

	mux := http.NewServeMux()
	mux.HandleFunc("GET /player", s.handlePlayer)
	mux.HandleFunc("GET /api/now-playing", s.handleNowPlaying)
	mux.HandleFunc("GET /api/sysinfo", handleSysInfo)
	mux.Handle("GET /assets/", http.StripPrefix("/assets/", http.FileServer(http.FS(sub))))

	srv := &http.Server{Handler: mux}

	ln, err := net.Listen("tcp", s.listenAddr)
	if err != nil {
		return err
	}

	go func() {
		<-ctx.Done()
		srv.Close()
	}()

	if err := srv.Serve(ln); err != http.ErrServerClosed {
		return err
	}
	return nil
}

func (s *Server) handlePlayer(w http.ResponseWriter, _ *http.Request) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.Write([]byte(playerHTML)) //nolint:errcheck
}

func (s *Server) handleNowPlaying(w http.ResponseWriter, _ *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(s.nowFn()) //nolint:errcheck
}

func handleSysInfo(w http.ResponseWriter, _ *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(collectSysInfo()) //nolint:errcheck
}

// collectSysInfo builds the list of info items shown in the overlay.
// To add more items, append to the slice here.
func collectSysInfo() SysInfo {
	var items []InfoItem
	if h, err := os.Hostname(); err == nil {
		items = append(items, InfoItem{Label: "Hostname", Value: h})
	}
	if up := readUptime(); up != "" {
		items = append(items, InfoItem{Label: "Uptime", Value: up})
	}
	return SysInfo{Items: items}
}

func readUptime() string {
	data, err := os.ReadFile("/proc/uptime")
	if err != nil {
		return ""
	}
	parts := strings.Fields(string(data))
	if len(parts) == 0 {
		return ""
	}
	secs, err := strconv.ParseFloat(parts[0], 64)
	if err != nil {
		return ""
	}
	d := time.Duration(secs) * time.Second
	days := int(d.Hours()) / 24
	hours := int(d.Hours()) % 24
	mins := int(d.Minutes()) % 60
	if days > 0 {
		return fmt.Sprintf("%dd %dh %dm", days, hours, mins)
	}
	if hours > 0 {
		return fmt.Sprintf("%dh %dm", hours, mins)
	}
	return fmt.Sprintf("%dm", mins)
}

const playerHTML = `<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="UTF-8">
  <title>Morz Infoboard</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    html, body { width: 100%; height: 100%; background: #000; overflow: hidden; }

    /* Splash-Hintergrund – Bild wird per JS orientierungsabhängig gesetzt */
    #splash {
      position: fixed; inset: 0;
      background-size: cover;
      background-position: center;
      background-repeat: no-repeat;
    }

    /* Unteres-Drittel-Overlay mit Systeminformationen */
    #info-overlay {
      position: fixed; bottom: 0; left: 0; right: 0;
      height: 33.3%;
      background: linear-gradient(
        to bottom,
        rgba(0,0,0,0)   0%,
        rgba(0,0,0,0.7) 40%,
        rgba(0,0,0,0.85) 100%
      );
      display: flex;
      align-items: flex-end;
      padding: 0 3% 2.5%;
      gap: 3rem;
      z-index: 1;
    }
    .info-item {
      display: flex;
      flex-direction: column;
    }
    .info-item .label {
      font-family: sans-serif;
      font-size: 0.7rem;
      letter-spacing: 0.12em;
      text-transform: uppercase;
      color: rgba(255,255,255,0.5);
      margin-bottom: 0.25em;
    }
    .info-item .value {
      font-family: monospace;
      font-size: 1.1rem;
      font-weight: 500;
      letter-spacing: 0.03em;
      color: #fff;
    }

    /* Inhalts-iframe – liegt über dem Splash, wenn eine URL gesetzt ist */
    #frame {
      position: fixed; inset: 0;
      width: 100%; height: 100%;
      border: none;
      display: none;
      z-index: 10;
    }

    /* Verbindungsstatus-Punkt */
    #dot {
      position: fixed; bottom: 10px; right: 10px;
      width: 10px; height: 10px; border-radius: 50%;
      background: #444; opacity: 0.6;
      z-index: 9999;
      transition: background 0.5s;
    }
    #dot.online   { background: #4caf50; }
    #dot.degraded { background: #ff9800; }
    #dot.offline  { background: #f44336; }
  </style>
</head>
<body>
  <div id="splash"></div>
  <div id="info-overlay"></div>
  <iframe id="frame" allow="autoplay; fullscreen" allowfullscreen></iframe>
  <div id="dot"></div>

  <script>
    var splash  = document.getElementById('splash');
    var overlay = document.getElementById('info-overlay');
    var frame   = document.getElementById('frame');
    var dot     = document.getElementById('dot');

    // Orientierungsgerechtes Splash-Bild wählen
    function updateSplash() {
      var portrait = window.innerHeight > window.innerWidth;
      splash.style.backgroundImage = portrait
        ? 'url(/assets/splash-portrait.png)'
        : 'url(/assets/splash-landscape.png)';
    }
    updateSplash();
    window.addEventListener('resize', updateSplash);

    // Sysinfo-Overlay rendern
    function renderSysInfo(items) {
      overlay.innerHTML = '';
      (items || []).forEach(function(item) {
        var el = document.createElement('div');
        el.className = 'info-item';
        el.innerHTML =
          '<span class="label">' + esc(item.label) + '</span>' +
          '<span class="value">' + esc(item.value) + '</span>';
        overlay.appendChild(el);
      });
    }

    function esc(s) {
      return String(s)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;');
    }

    // Now-Playing anwenden: iframe zeigen wenn URL gesetzt, sonst Splash
    function applyNowPlaying(data) {
      dot.className = data.connectivity || '';
      if (data.url) {
        if (frame.src !== data.url) { frame.src = data.url; }
        frame.style.display = '';
      } else {
        frame.style.display = 'none';
      }
    }

    function pollSysInfo() {
      fetch('/api/sysinfo')
        .then(function(r) { return r.json(); })
        .then(function(d) { renderSysInfo(d.items); })
        .catch(function() {});
    }

    function pollNowPlaying() {
      fetch('/api/now-playing')
        .then(function(r) { return r.json(); })
        .then(applyNowPlaying)
        .catch(function() { dot.className = 'offline'; });
    }

    pollSysInfo();
    pollNowPlaying();
    setInterval(pollSysInfo,    30000);
    setInterval(pollNowPlaying, 15000);
  </script>
</body>
</html>`
