// Package tenant implements the tenant self-service dashboard UI.
package tenant

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

	"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 "📁"
		}
	},
	"humanSize": func(b int64) string {
		switch {
		case b >= 1<<20:
			return fmt.Sprintf("%.1f MB", float64(b)/float64(1<<20))
		case b >= 1<<10:
			return fmt.Sprintf("%.0f KB", float64(b)/float64(1<<10))
		default:
			return fmt.Sprintf("%d B", b)
		}
	},
}

const maxUploadSize = 512 << 20 // 512 MB

// HandleTenantDashboard renders the tenant self-service dashboard.
func HandleTenantDashboard(
	tenantStore *store.TenantStore,
	screenStore *store.ScreenStore,
	mediaStore *store.MediaStore,
) http.HandlerFunc {
	t := template.Must(template.New("tenant-dash").Funcs(tmplFuncs).Parse(tenantDashTmpl))
	return func(w http.ResponseWriter, r *http.Request) {
		tenantSlug := r.PathValue("tenantSlug")

		tenant, err := tenantStore.Get(r.Context(), tenantSlug)
		if err != nil {
			http.Error(w, "Tenant nicht gefunden: "+tenantSlug, http.StatusNotFound)
			return
		}

		screens, err := screenStore.List(r.Context(), tenant.ID)
		if err != nil {
			http.Error(w, "db error", http.StatusInternalServerError)
			return
		}

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

		// Flash message: prefer ?flash= (from tenant upload redirect),
		// also accept legacy ?msg= used by manage handlers.
		flash := ""
		if f := r.URL.Query().Get("flash"); f != "" {
			switch f {
			case "uploaded":
				flash = "Medium erfolgreich hochgeladen."
			case "deleted":
				flash = "Medium erfolgreich gelöscht."
			default:
				flash = f
			}
		} else if m := r.URL.Query().Get("msg"); m != "" {
			switch m {
			case "uploaded":
				flash = "Medium erfolgreich hochgeladen."
			case "deleted":
				flash = "Medium erfolgreich gelöscht."
			default:
				flash = m
			}
		}

		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		t.Execute(w, map[string]any{ //nolint:errcheck
			"Tenant":  tenant,
			"Screens": screens,
			"Assets":  assets,
			"Flash":   flash,
		})
	}
}

// HandleTenantUpload handles multipart file uploads and web-URL registrations
// from the tenant dashboard, then redirects back.
func HandleTenantUpload(
	tenantStore *store.TenantStore,
	mediaStore *store.MediaStore,
	uploadDir string,
) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tenantSlug := r.PathValue("tenantSlug")

		tenant, err := tenantStore.Get(r.Context(), tenantSlug)
		if err != nil {
			http.Error(w, "Tenant nicht gefunden: "+tenantSlug, 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 = mediaStore.Create(r.Context(), tenant.ID, 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")
			// Derive asset type from MIME if more specific.
			if detected := mimeToAssetType(mimeType); detected != "" {
				assetType = detected
			}
			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, cerr := io.Copy(dest, file)
			if cerr != nil {
				os.Remove(destPath) //nolint:errcheck
				http.Error(w, "Schreibfehler", http.StatusInternalServerError)
				return
			}
			storagePath := "/uploads/" + filename
			_, err = mediaStore.Create(r.Context(), tenant.ID, 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, "/tenant/"+tenantSlug+"/dashboard?tab=media&flash=uploaded", http.StatusSeeOther)
	}
}

// HandleTenantDeleteMedia deletes a media asset owned by the tenant.
func HandleTenantDeleteMedia(
	tenantStore *store.TenantStore,
	mediaStore *store.MediaStore,
	uploadDir string,
) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tenantSlug := r.PathValue("tenantSlug")
		mediaID := r.PathValue("mediaId")

		// Verify tenant exists.
		tenant, err := tenantStore.Get(r.Context(), tenantSlug)
		if err != nil {
			http.Error(w, "Tenant nicht gefunden", http.StatusNotFound)
			return
		}

		asset, err := mediaStore.Get(r.Context(), mediaID)
		if err != nil {
			http.Error(w, "Medium nicht gefunden", http.StatusNotFound)
			return
		}
		// Ownership check.
		if asset.TenantID != tenant.ID {
			http.Error(w, "Forbidden", http.StatusForbidden)
			return
		}
		if asset.StoragePath != "" {
			os.Remove(filepath.Join(uploadDir, filepath.Base(asset.StoragePath))) //nolint:errcheck
		}
		mediaStore.Delete(r.Context(), mediaID) //nolint:errcheck

		http.Redirect(w, r, "/tenant/"+tenantSlug+"/dashboard?tab=media&flash=deleted", http.StatusSeeOther)
	}
}

// mimeToAssetType derives the asset type from a MIME type string.
func mimeToAssetType(mime string) string {
	mime = strings.ToLower(strings.TrimSpace(mime))
	switch {
	case strings.HasPrefix(mime, "image/"):
		return "image"
	case strings.HasPrefix(mime, "video/"):
		return "video"
	case mime == "application/pdf":
		return "pdf"
	default:
		return ""
	}
}

// sanitize converts a string to a safe filename component.
func sanitize(s string) string {
	var b strings.Builder
	for _, r := range s {
		if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || r == '_' {
			b.WriteRune(r)
		} else {
			b.WriteRune('_')
		}
	}
	out := b.String()
	if len(out) > 40 {
		out = out[:40]
	}
	return out
}
