package manage

import (
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"git.az-it.net/az/morz-infoboard/server/backend/internal/mqttnotifier"
	"git.az-it.net/az/morz-infoboard/server/backend/internal/reqcontext"
	"git.az-it.net/az/morz-infoboard/server/backend/internal/store"
)

var tmplFuncs = template.FuncMap{
	"typeIcon": func(t string) string {
		switch t {
		case "image":
			return "🖼"
		case "video":
			return "🎬"
		case "pdf":
			return "📄"
		case "web":
			return "🌐"
		default:
			return "📁"
		}
	},
	"orientationLabel": func(o string) string {
		if o == "portrait" {
			return "Hochformat"
		}
		return "Querformat"
	},
	"shortSrc": func(s string) string {
		if len(s) > 60 {
			return s[:57] + "..."
		}
		return s
	},
	"formatDT": func(t *time.Time) string {
		if t == nil {
			return ""
		}
		return t.Format("2006-01-02T15:04")
	},
}

// HandleAdminUI renders the admin overview page.
func HandleAdminUI(tenants *store.TenantStore, screens *store.ScreenStore) http.HandlerFunc {
	t := template.Must(template.New("admin").Funcs(tmplFuncs).Parse(adminTmpl))
	return func(w http.ResponseWriter, r *http.Request) {
		allScreens, err := screens.ListAll(r.Context())
		if err != nil {
			http.Error(w, "db error", http.StatusInternalServerError)
			return
		}
		allTenants, err := tenants.List(r.Context())
		if err != nil {
			http.Error(w, "db error", http.StatusInternalServerError)
			return
		}
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		t.Execute(w, map[string]any{ //nolint:errcheck
			"Screens": allScreens,
			"Tenants": allTenants,
		})
	}
}

// HandleManageUI renders the playlist management UI for a specific screen.
func HandleManageUI(
	tenants *store.TenantStore,
	screens *store.ScreenStore,
	media *store.MediaStore,
	playlists *store.PlaylistStore,
) http.HandlerFunc {
	t := template.Must(template.New("manage").Funcs(tmplFuncs).Parse(manageTmpl))
	return func(w http.ResponseWriter, r *http.Request) {
		screenSlug := r.PathValue("screenSlug")

		screen, err := screens.GetBySlug(r.Context(), screenSlug)
		if err != nil {
			http.Error(w, "Screen nicht gefunden: "+screenSlug, http.StatusNotFound)
			return
		}

		var tenant *store.Tenant
		if u := reqcontext.UserFromContext(r.Context()); u != nil && u.TenantSlug != "" {
			tenant, _ = tenants.Get(r.Context(), u.TenantSlug)
		}
		if tenant == nil {
			tenant = &store.Tenant{ID: screen.TenantID, Name: "MORZ"}
		}

		playlist, err := playlists.GetOrCreateForScreen(r.Context(), screen.TenantID, screen.ID, screen.Name)
		if err != nil {
			http.Error(w, "db error", http.StatusInternalServerError)
			return
		}

		items, err := playlists.ListItems(r.Context(), playlist.ID)
		if err != nil {
			http.Error(w, "db error", http.StatusInternalServerError)
			return
		}

		assets, err := media.List(r.Context(), screen.TenantID)
		if err != nil {
			http.Error(w, "db error", http.StatusInternalServerError)
			return
		}

		// Build set of already-added asset IDs to mark them in library.
		addedAssets := map[string]bool{}
		for _, it := range items {
			if it.MediaAssetID != "" {
				addedAssets[it.MediaAssetID] = true
			}
		}

		// Determine back-navigation based on ?from= query parameter.
		backLink := "/admin"
		backLabel := "← Admin"
		if r.URL.Query().Get("from") == "tenant" {
			tenantSlug := ""
			if u := reqcontext.UserFromContext(r.Context()); u != nil && u.TenantSlug != "" {
				tenantSlug = u.TenantSlug
			}
			if tenantSlug != "" {
				backLink = "/tenant/" + tenantSlug + "/dashboard"
				backLabel = "← Dashboard"
			}
		}

		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		t.Execute(w, map[string]any{ //nolint:errcheck
			"Screen":      screen,
			"Tenant":      tenant,
			"Playlist":    playlist,
			"Items":       items,
			"Assets":      assets,
			"AddedAssets": addedAssets,
			"BackLink":    backLink,
			"BackLabel":   backLabel,
		})
	}
}

// HandleCreateScreenUI handles form POST to create a screen, then redirects.
func HandleCreateScreenUI(tenants *store.TenantStore, screens *store.ScreenStore) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if err := r.ParseForm(); err != nil {
			http.Error(w, "bad form", http.StatusBadRequest)
			return
		}
		slug := strings.TrimSpace(r.FormValue("slug"))
		name := strings.TrimSpace(r.FormValue("name"))
		orientation := r.FormValue("orientation")
		if slug == "" || name == "" {
			http.Error(w, "slug und name erforderlich", http.StatusBadRequest)
			return
		}
		if orientation == "" {
			orientation = "landscape"
		}

		tenantSlug := "morz"
		if u := reqcontext.UserFromContext(r.Context()); u != nil && u.TenantSlug != "" {
			tenantSlug = u.TenantSlug
		}
		tenant, err := tenants.Get(r.Context(), tenantSlug)
		if err != nil {
			http.Error(w, "tenant nicht gefunden", http.StatusInternalServerError)
			return
		}

		_, err = screens.Create(r.Context(), tenant.ID, slug, name, orientation)
		if err != nil {
			http.Error(w, "Fehler: "+err.Error(), http.StatusInternalServerError)
			return
		}
		http.Redirect(w, r, "/admin?msg=added", http.StatusSeeOther)
	}
}

// HandleProvisionUI creates a screen in DB and shows the Ansible setup instructions.
func HandleProvisionUI(tenants *store.TenantStore, screens *store.ScreenStore) http.HandlerFunc {
	t := template.Must(template.New("provision").Funcs(tmplFuncs).Parse(provisionTmpl))
	return func(w http.ResponseWriter, r *http.Request) {
		if err := r.ParseForm(); err != nil {
			http.Error(w, "bad form", http.StatusBadRequest)
			return
		}
		slug := strings.TrimSpace(r.FormValue("slug"))
		name := strings.TrimSpace(r.FormValue("name"))
		ip := strings.TrimSpace(r.FormValue("ip"))
		sshUser := strings.TrimSpace(r.FormValue("ssh_user"))
		orientation := r.FormValue("orientation")

		if slug == "" || ip == "" {
			http.Error(w, "slug und IP-Adresse erforderlich", http.StatusBadRequest)
			return
		}
		if name == "" {
			name = slug
		}
		if sshUser == "" {
			sshUser = "morz"
		}
		if orientation == "" {
			orientation = "landscape"
		}

		tenantSlug := "morz"
		if u := reqcontext.UserFromContext(r.Context()); u != nil && u.TenantSlug != "" {
			tenantSlug = u.TenantSlug
		}
		tenant, err := tenants.Get(r.Context(), tenantSlug)
		if err != nil {
			http.Error(w, "tenant nicht gefunden", http.StatusInternalServerError)
			return
		}

		screen, err := screens.Upsert(r.Context(), tenant.ID, slug, name, orientation)
		if err != nil {
			http.Error(w, "DB-Fehler: "+err.Error(), http.StatusInternalServerError)
			return
		}

		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		t.Execute(w, map[string]any{ //nolint:errcheck
			"Screen":      screen,
			"IP":          ip,
			"SSHUser":     sshUser,
			"Orientation": orientation,
		})
	}
}

// HandleDeleteScreenUI handles DELETE for a screen, then redirects.
func HandleDeleteScreenUI(screens *store.ScreenStore) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("screenId")
		if err := screens.Delete(r.Context(), id); err != nil {
			http.Error(w, "db error", http.StatusInternalServerError)
			return
		}
		http.Redirect(w, r, "/admin?msg=deleted", http.StatusSeeOther)
	}
}

// HandleUploadMediaUI handles form upload from the manage UI and redirects back.
func HandleUploadMediaUI(media *store.MediaStore, screens *store.ScreenStore, uploadDir string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		screenSlug := r.PathValue("screenSlug")

		screen, err := screens.GetBySlug(r.Context(), screenSlug)
		if err != nil {
			http.Error(w, "screen nicht gefunden", http.StatusNotFound)
			return
		}

		if err := r.ParseMultipartForm(maxUploadSize); err != nil {
			http.Error(w, "Upload zu groß oder ungültig", http.StatusBadRequest)
			return
		}

		assetType := strings.TrimSpace(r.FormValue("type"))
		title := strings.TrimSpace(r.FormValue("title"))

		switch assetType {
		case "web":
			url := strings.TrimSpace(r.FormValue("url"))
			if url == "" {
				http.Error(w, "URL erforderlich", http.StatusBadRequest)
				return
			}
			if title == "" {
				title = url
			}
			_, err = media.Create(r.Context(), screen.TenantID, title, "web", "", url, "", 0)
		case "image", "video", "pdf":
			file, header, ferr := r.FormFile("file")
			if ferr != nil {
				http.Error(w, "Datei erforderlich", http.StatusBadRequest)
				return
			}
			defer file.Close()
			if title == "" {
				title = strings.TrimSuffix(header.Filename, filepath.Ext(header.Filename))
			}
			mimeType := header.Header.Get("Content-Type")
			ext := filepath.Ext(header.Filename)
			filename := fmt.Sprintf("%d_%s%s", time.Now().UnixNano(), sanitize(title), ext)
			destPath := filepath.Join(uploadDir, filename)
			dest, ferr := os.Create(destPath)
			if ferr != nil {
				http.Error(w, "Speicherfehler", http.StatusInternalServerError)
				return
			}
			defer dest.Close()
			size, _ := io.Copy(dest, file)
			storagePath := "/uploads/" + filename
			_, err = media.Create(r.Context(), screen.TenantID, title, assetType, storagePath, "", mimeType, size)
		default:
			http.Error(w, "Unbekannter Typ", http.StatusBadRequest)
			return
		}

		if err != nil {
			http.Error(w, "DB-Fehler: "+err.Error(), http.StatusInternalServerError)
			return
		}
		http.Redirect(w, r, "/manage/"+screenSlug+"?msg=uploaded", http.StatusSeeOther)
	}
}

// HandleAddItemUI handles form POST to add a playlist item, then redirects.
func HandleAddItemUI(playlists *store.PlaylistStore, media *store.MediaStore, screens *store.ScreenStore, notifier *mqttnotifier.Notifier) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		screenSlug := r.PathValue("screenSlug")
		if err := r.ParseForm(); err != nil {
			http.Error(w, "bad form", http.StatusBadRequest)
			return
		}

		screen, err := screens.GetBySlug(r.Context(), screenSlug)
		if err != nil {
			http.Error(w, "screen nicht gefunden", http.StatusNotFound)
			return
		}

		playlist, err := playlists.GetOrCreateForScreen(r.Context(), screen.TenantID, screen.ID, screen.Name)
		if err != nil {
			http.Error(w, "db error", http.StatusInternalServerError)
			return
		}

		mediaAssetID := r.FormValue("media_asset_id")
		itemType := r.FormValue("type")
		src := r.FormValue("src")
		title := r.FormValue("title")
		durationSeconds := 20
		if d, err := strconv.Atoi(strings.TrimSpace(r.FormValue("duration_seconds"))); err == nil && d > 0 {
			durationSeconds = d
		}
		validFrom, _ := parseOptionalTime(r.FormValue("valid_from"))
		validUntil, _ := parseOptionalTime(r.FormValue("valid_until"))

		if mediaAssetID != "" {
			asset, err := media.Get(r.Context(), mediaAssetID)
			if err != nil {
				http.Error(w, "Medium nicht gefunden", http.StatusBadRequest)
				return
			}
			itemType = asset.Type
			if asset.StoragePath != "" {
				src = asset.StoragePath
			} else {
				src = asset.OriginalURL
			}
			if title == "" {
				title = asset.Title
			}
		}

		if itemType == "" || src == "" {
			http.Error(w, "type und src erforderlich", http.StatusBadRequest)
			return
		}

		_, err = playlists.AddItem(r.Context(), playlist.ID, mediaAssetID,
			itemType, src, title, durationSeconds, validFrom, validUntil)
		if err != nil {
			http.Error(w, "db error", http.StatusInternalServerError)
			return
		}
		notifier.NotifyChanged(screenSlug)
		http.Redirect(w, r, "/manage/"+screenSlug+"?msg=added", http.StatusSeeOther)
	}
}

// HandleDeleteItemUI removes a playlist item and redirects back.
func HandleDeleteItemUI(playlists *store.PlaylistStore, notifier *mqttnotifier.Notifier) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		screenSlug := r.PathValue("screenSlug")
		itemID := r.PathValue("itemId")
		if err := playlists.DeleteItem(r.Context(), itemID); err != nil {
			http.Error(w, "db error", http.StatusInternalServerError)
			return
		}
		notifier.NotifyChanged(screenSlug)
		http.Redirect(w, r, "/manage/"+screenSlug+"?msg=deleted", http.StatusSeeOther)
	}
}

// HandleReorderUI accepts JSON body with ordered IDs (HTMX/fetch).
func HandleReorderUI(playlists *store.PlaylistStore, screens *store.ScreenStore, notifier *mqttnotifier.Notifier) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		screenSlug := r.PathValue("screenSlug")
		screen, err := screens.GetBySlug(r.Context(), screenSlug)
		if err != nil {
			http.Error(w, "screen nicht gefunden", http.StatusNotFound)
			return
		}
		playlist, err := playlists.GetByScreen(r.Context(), screen.ID)
		if err != nil {
			http.Error(w, "playlist nicht gefunden", http.StatusNotFound)
			return
		}
		var ids []string
		if err := json.NewDecoder(r.Body).Decode(&ids); err != nil {
			http.Error(w, "JSON erwartet: array von item-IDs", http.StatusBadRequest)
			return
		}
		if err := playlists.Reorder(r.Context(), playlist.ID, ids); err != nil {
			http.Error(w, "db error", http.StatusInternalServerError)
			return
		}
		notifier.NotifyChanged(screenSlug)
		w.WriteHeader(http.StatusNoContent)
	}
}

// HandleUpdateItemUI handles form PATCH/POST to update a single item.
func HandleUpdateItemUI(playlists *store.PlaylistStore, notifier *mqttnotifier.Notifier) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		screenSlug := r.PathValue("screenSlug")
		itemID := r.PathValue("itemId")
		if err := r.ParseForm(); err != nil {
			http.Error(w, "bad form", http.StatusBadRequest)
			return
		}
		title := r.FormValue("title")
		durationSeconds := 20
		if d, err := strconv.Atoi(strings.TrimSpace(r.FormValue("duration_seconds"))); err == nil && d > 0 {
			durationSeconds = d
		}
		enabled := r.FormValue("enabled") == "true"
		validFrom, _ := parseOptionalTime(r.FormValue("valid_from"))
		validUntil, _ := parseOptionalTime(r.FormValue("valid_until"))

		if err := playlists.UpdateItem(r.Context(), itemID, title, durationSeconds, enabled, validFrom, validUntil); err != nil {
			http.Error(w, "db error", http.StatusInternalServerError)
			return
		}
		notifier.NotifyChanged(screenSlug)
		http.Redirect(w, r, "/manage/"+screenSlug+"?msg=saved", http.StatusSeeOther)
	}
}

// HandleDeleteMediaUI deletes media and redirects back.
func HandleDeleteMediaUI(media *store.MediaStore, screens *store.ScreenStore, uploadDir string, notifier *mqttnotifier.Notifier) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		screenSlug := r.PathValue("screenSlug")
		mediaID := r.PathValue("mediaId")

		asset, err := media.Get(r.Context(), mediaID)
		if err == nil && asset.StoragePath != "" {
			os.Remove(filepath.Join(uploadDir, filepath.Base(asset.StoragePath))) //nolint:errcheck
		}
		media.Delete(r.Context(), mediaID) //nolint:errcheck

		notifier.NotifyChanged(screenSlug)
		http.Redirect(w, r, "/manage/"+screenSlug+"?msg=deleted", http.StatusSeeOther)
	}
}
