package router

import (
	"bookhoard/internal/database"
	"bookhoard/internal/handlers"
	"bookhoard/templates"
	"bytes"
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5/pgtype"
	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/labstack/echo/v5"
)

func registerAdminLibraryRoutes(cfg *Config, frontendProtected *echo.Group) {
	g := frontendProtected.Group("", handlers.AdminMiddleware)

	// HTMX: Create library
	g.POST("/admin/library/create", func(c *echo.Context) error {
		user := c.Get("user").(database.Users)

		name := c.FormValue("name")
		desc := c.FormValue("description")
		libType := c.FormValue("type")
		if name == "" || libType == "" {
			return c.HTML(http.StatusBadRequest, `<div class="text-sm" style="color: var(--status-danger);">Name and type are required</div>`)
		}

		_, err := cfg.LibraryService.CreateLibrary(
			c.Request().Context(),
			name,
			desc,
			libType,
			user.ID,
		)
		if err != nil {
			return c.HTML(http.StatusInternalServerError, `<div class="text-sm" style="color: var(--status-danger);">Failed to create library</div>`)
		}

		return renderLibraryList(c, cfg)
	})

	// HTMX: Update library
	g.PUT("/admin/library/:id", func(c *echo.Context) error {
		libraryID, err := parseAdminUUID(c.Param("id"))
		if err != nil {
			return c.HTML(http.StatusBadRequest, `<div class="text-sm" style="color: var(--status-danger);">Invalid library ID</div>`)
		}

		name := c.FormValue("name")
		desc := c.FormValue("description")
		if name == "" {
			return c.HTML(http.StatusBadRequest, `<div class="text-sm" style="color: var(--status-danger);">Name is required</div>`)
		}

		_, err = cfg.LibraryService.UpdateLibrary(c.Request().Context(), libraryID, name, desc)
		if err != nil {
			return c.HTML(http.StatusInternalServerError, `<div class="text-sm" style="color: var(--status-danger);">Failed to update library</div>`)
		}

		return renderLibraryList(c, cfg)
	})

	// HTMX: Delete library
	g.DELETE("/admin/library/:id", func(c *echo.Context) error {
		libraryID, err := parseAdminUUID(c.Param("id"))
		if err != nil {
			return c.HTML(http.StatusBadRequest, `<div class="text-sm" style="color: var(--status-danger);">Invalid library ID</div>`)
		}

		err = cfg.LibraryService.DeleteLibrary(c.Request().Context(), libraryID)
		if err != nil {
			return c.HTML(http.StatusInternalServerError, `<div class="text-sm" style="color: var(--status-danger);">Failed to delete library</div>`)
		}

		return renderLibraryList(c, cfg)
	})

	// HTMX: Library expanded panel
	g.GET("/admin/library/:id/panel", func(c *echo.Context) error {
		libraryID, err := parseAdminUUID(c.Param("id"))
		if err != nil {
			return c.HTML(http.StatusBadRequest, `<div class="text-sm" style="color: var(--status-danger);">Invalid library ID</div>`)
		}

		return renderLibraryPanel(c, cfg, libraryID)
	})

	// HTMX: Add folder
	g.POST("/admin/library/:id/folders", func(c *echo.Context) error {
		libraryID, err := parseAdminUUID(c.Param("id"))
		if err != nil {
			return c.HTML(http.StatusBadRequest, `<div class="text-sm" style="color: var(--status-danger);">Invalid library ID</div>`)
		}

		folderPath := c.FormValue("folder_path")
		if folderPath == "" {
			return renderLibraryPanel(c, cfg, libraryID)
		}

		if strings.Contains(folderPath, "..") {
			return renderLibraryPanelWithError(c, cfg, libraryID, "Path traversal not allowed")
		}

		cleanPath := filepath.Clean(folderPath)
		fileInfo, err := os.Stat(cleanPath)
		if err != nil {
			return renderLibraryPanelWithError(c, cfg, libraryID, "Folder path does not exist")
		}
		if !fileInfo.IsDir() {
			return renderLibraryPanelWithError(c, cfg, libraryID, "Path must be a directory")
		}

		_, err = cfg.LibraryService.AddLibraryFolder(c.Request().Context(), libraryID, cleanPath)
		if err != nil {
			return renderLibraryPanelWithError(c, cfg, libraryID, "Failed to add folder: "+err.Error())
		}

		return renderLibraryPanel(c, cfg, libraryID)
	})

	// HTMX: Remove folder
	g.DELETE("/admin/library/:id/folders", func(c *echo.Context) error {
		libraryID, err := parseAdminUUID(c.Param("id"))
		if err != nil {
			return c.HTML(http.StatusBadRequest, `<div class="text-sm" style="color: var(--status-danger);">Invalid library ID</div>`)
		}

		folderPath := c.FormValue("folder_path")
		if folderPath == "" {
			return renderLibraryPanel(c, cfg, libraryID)
		}

		err = cfg.LibraryService.DeleteLibraryFolder(c.Request().Context(), libraryID, folderPath)
		if err != nil {
			return renderLibraryPanelWithError(c, cfg, libraryID, "Failed to remove folder")
		}

		return renderLibraryPanel(c, cfg, libraryID)
	})

	// HTMX: Folder browser
	g.GET("/admin/library/browse", func(c *echo.Context) error {
		path := c.QueryParam("path")
		if path == "" {
			path = "/"
		}
		targetInput := c.QueryParam("target_input")
		libraryID := c.QueryParam("library_id")

		dirs, currentPath, parentPath, err := cfg.LibraryService.BrowseDirectories(c.Request().Context(), path)
		if err != nil {
			return c.HTML(http.StatusBadRequest, `<div class="text-sm" style="color: var(--status-danger);">Cannot browse: `+err.Error()+`</div>`)
		}

		entries := make([]templates.DirEntry, len(dirs))
		for i, d := range dirs {
			fullPath := filepath.Join(currentPath, d)
			entries[i] = templates.DirEntry{Name: d, Path: fullPath}
		}

		var buf bytes.Buffer
		err = templates.FolderBrowserContent(currentPath, parentPath, entries, targetInput, libraryID).Render(c.Request().Context(), &buf)
		if err != nil {
			return err
		}
		return c.HTML(http.StatusOK, buf.String())
	})

	// HTMX: Set user visibility for library
	g.POST("/admin/library/:id/visibility", func(c *echo.Context) error {
		libraryID, err := parseAdminUUID(c.Param("id"))
		if err != nil {
			return c.HTML(http.StatusBadRequest, `<div class="text-sm" style="color: var(--status-danger);">Invalid library ID</div>`)
		}

		userIDStr := c.FormValue("user_id")
		isVisible := c.FormValue("is_visible") == "true"

		userID, err := parseAdminUUID(userIDStr)
		if err != nil {
			return c.HTML(http.StatusBadRequest, `<div class="text-sm" style="color: var(--status-danger);">Invalid user ID</div>`)
		}

		_, err = cfg.LibraryService.SetLibraryVisibility(c.Request().Context(), userID, libraryID, isVisible)
		if err != nil {
			return c.HTML(http.StatusInternalServerError, `<div class="text-sm" style="color: var(--status-danger);">Failed to update visibility</div>`)
		}

		return renderLibraryPanel(c, cfg, libraryID)
	})
}

// renderLibraryList fetches all libraries + users and renders the LibraryList partial.
func renderLibraryList(c *echo.Context, cfg *Config) error {
	libraries, err := cfg.LibraryHandler.ListLibrariesData(c.Request().Context())
	if err != nil {
		return c.HTML(http.StatusInternalServerError, `<div class="text-sm" style="color: var(--status-danger);">Failed to load libraries</div>`)
	}

	libData := make([]templates.LibraryData, len(libraries))
	for i, lib := range libraries {
		libUUID, _ := uuid.FromBytes(lib.ID.Bytes[0:16])
		folderCount := getFolderCount(c.Request().Context(), cfg, lib.ID)
		libData[i] = templates.LibraryData{
			ID:          libUUID.String(),
			Name:        lib.Name,
			Description: getText(lib.Description),
			TypeName:    lib.TypeName,
			TypeValue:   lib.TypeName,
			FolderCount: folderCount,
		}
	}

	users, err := cfg.Queries.ListUsers(c.Request().Context())
	if err != nil {
		log.Printf("ListUsers failed: %v", err)
		users = []database.ListUsersRow{}
	}
	userData := make([]templates.User, len(users))
	for i, u := range users {
		userUUID, _ := uuid.FromBytes(u.ID.Bytes[0:16])
		userData[i] = templates.User{
			ID:       userUUID.String(),
			Username: u.Username,
			Email:    u.Email,
			Role:     u.Role,
		}
	}

	var buf bytes.Buffer
	err = templates.LibraryList(templates.User{}, libData, userData).Render(c.Request().Context(), &buf)
	if err != nil {
		return err
	}
	return c.HTML(http.StatusOK, buf.String())
}

// renderLibraryPanel fetches library details and renders the LibraryPanel partial.
func renderLibraryPanel(c *echo.Context, cfg *Config, libraryID pgtype.UUID) error {
	return renderLibraryPanelWithError(c, cfg, libraryID, "")
}

func renderLibraryPanelWithError(c *echo.Context, cfg *Config, libraryID pgtype.UUID, errMsg string) error {
	ctx := c.Request().Context()
	libraryIDStr := uuid.UUID(libraryID.Bytes).String()

	// Get library details
	lib, err := cfg.LibraryService.GetLibrary(ctx, libraryID)
	if err != nil {
		return c.HTML(http.StatusNotFound, `<div class="text-sm" style="color: var(--status-danger);">Library not found</div>`)
	}

	libData := templates.LibraryData{
		ID:          libraryIDStr,
		Name:        lib.Name,
		Description: getText(lib.Description),
		TypeName:    lib.TypeName,
		TypeValue:   lib.TypeName,
	}

	// Get folders
	dbFolders, err := cfg.LibraryService.GetLibraryFolders(ctx, libraryID)
	if err != nil {
		log.Printf("GetLibraryFolders failed: %v", err)
	}
	folders := make([]templates.FolderData, len(dbFolders))
	for i, f := range dbFolders {
		folders[i] = templates.FolderData{FolderPath: f.FolderPath}
	}
	libData.FolderCount = len(folders)

	// Get users
	dbUsers, err := cfg.Queries.ListUsers(ctx)
	if err != nil {
		log.Printf("ListUsers failed: %v", err)
		dbUsers = []database.ListUsersRow{}
	}
	userData := make([]templates.User, len(dbUsers))
	for i, u := range dbUsers {
		userUUID, _ := uuid.FromBytes(u.ID.Bytes[0:16])
		userData[i] = templates.User{
			ID:       userUUID.String(),
			Username: u.Username,
			Email:    u.Email,
		}
	}

	// Get visibility for all users
	visibility := make([]templates.UserVisibilityData, len(userData))
	for i, u := range userData {
		userUUID, _ := parseAdminUUID(u.ID)
		visibleLibs, err := cfg.LibraryService.GetUserVisibleLibraries(ctx, userUUID)
		if err != nil {
			log.Printf("GetUserVisibleLibraries failed: %v", err)
		}
		isVisible := false
		for _, vl := range visibleLibs {
			if vl.ID.Bytes == libraryID.Bytes {
				isVisible = true
				break
			}
		}
		visibility[i] = templates.UserVisibilityData{
			UserID:    u.ID,
			Username:  u.Username,
			Email:     u.Email,
			IsVisible: isVisible,
		}
	}

	// Get issue count
	issueStats, err := cfg.ProcessingIssuesHandler.GetProcessingIssueStatsData(ctx, libraryID)
	if err != nil {
		log.Printf("GetProcessingIssueStats failed: %v", err)
	}
	issueCount := issueStats.ErrorCount + issueStats.WarningCount + issueStats.InfoCount

	// Get current user for template
	tmplUser := templates.User{}
	if u, ok := c.Get("user").(database.Users); ok {
		userUUID, _ := uuid.FromBytes(u.ID.Bytes[0:16])
		tmplUser = templates.User{
			ID:       userUUID.String(),
			Username: u.Username,
			Role:     u.Role,
		}
	}

	var buf bytes.Buffer
	err = templates.LibraryPanel(tmplUser, libraryIDStr, libData, folders, userData, visibility, int(issueCount)).Render(ctx, &buf)
	if err != nil {
		return err
	}

	html := buf.String()
	if errMsg != "" {
		html = `<div class="p-3 mb-3 rounded-lg text-sm" style="background-color: color-mix(in srgb, var(--status-danger) 12%, var(--bg-secondary)); color: var(--status-danger);">` + errMsg + `</div>` + html
	}
	return c.HTML(http.StatusOK, html)
}

func getFolderCount(ctx context.Context, cfg *Config, libraryID pgtype.UUID) int {
	folders, err := cfg.LibraryService.GetLibraryFolders(ctx, libraryID)
	if err != nil {
		return 0
	}
	return len(folders)
}

func parseAdminUUID(s string) (pgtype.UUID, error) {
	parsed, err := uuid.Parse(s)
	if err != nil {
		return pgtype.UUID{}, err
	}
	return pgtype.UUID{Bytes: parsed, Valid: true}, nil
}

func getAdminStats(ctx context.Context, cfg *Config) templates.AdminStats {
	stats := templates.AdminStats{}

	libs, _ := cfg.LibraryHandler.ListLibrariesData(ctx)
	stats.LibraryCount = len(libs)

	users, _ := cfg.Queries.ListUsers(ctx)
	stats.UserCount = len(users)

	if pool, ok := cfg.DBPool.(*pgxpool.Pool); ok {
		_ = pool.QueryRow(ctx, "SELECT COUNT(*) FROM media_items").Scan(&stats.MediaCount)
		_ = pool.QueryRow(ctx, "SELECT COUNT(*) FROM devices").Scan(&stats.DeviceCount)
	}

	return stats
}

func registerAdminSettingsRoutes(cfg *Config, frontendProtected *echo.Group) {
	g := frontendProtected.Group("", handlers.AdminMiddleware)

	g.PUT("/admin/settings/scan", func(c *echo.Context) error {
		ctx := c.Request().Context()

		autoScan := c.FormValue("auto_scan_enabled") == "true"
		intervalStr := c.FormValue("scan_poll_interval_seconds")
		interval, err := strconv.Atoi(intervalStr)
		if err != nil || interval < 1 || interval > 3600 {
			return c.HTML(http.StatusBadRequest, `<div class="text-sm" style="color: var(--status-danger);">Interval must be between 1 and 3600 seconds</div>`)
		}

		autoScanStr := "false"
		if autoScan {
			autoScanStr = "true"
		}
		_ = cfg.Queries.UpdateSystemSetting(ctx, database.UpdateSystemSettingParams{
			SettingKey:   "auto_scan_enabled",
			SettingValue: autoScanStr,
		})
		_ = cfg.Queries.UpdateSystemSetting(ctx, database.UpdateSystemSettingParams{
			SettingKey:   "scan_poll_interval_seconds",
			SettingValue: strconv.Itoa(interval),
		})

		// Refresh the registry cache so the change is visible immediately.
		if cfg.Settings != nil {
			cfg.Settings.Reload(ctx)
		}

		scanSettings := templates.ScanSettingsData{
			AutoScanEnabled:         autoScan,
			ScanPollIntervalSeconds: interval,
		}
		var buf bytes.Buffer
		_ = templates.ScanSettingsSection(scanSettings).Render(ctx, &buf)
		return c.HTML(http.StatusOK, buf.String())
	})

	// HTMX endpoint for saving a single tunable setting. Returns a small HTML
	// status snippet rendered into the row's status span.
	g.PUT("/admin/settings/tunable", func(c *echo.Context) error {
		ctx := c.Request().Context()
		key := c.FormValue("key")
		value := c.FormValue("value")

		if cfg.SystemSettingsHandler == nil {
			return c.HTML(http.StatusServiceUnavailable, `<span style="color: var(--status-danger);">settings unavailable</span>`)
		}
		resp, err := cfg.SystemSettingsHandler.ApplySetting(ctx, key, value)
		if err != nil {
			return c.HTML(http.StatusBadRequest, fmt.Sprintf(`<span style="color: var(--status-danger);">%s</span>`, err.Error()))
		}

		color := "var(--status-success)"
		msg := "Saved"
		if resp.ReloadRequired {
			color = "var(--status-warning)"
			msg = "Saved — restart required"
		}
		return c.HTML(http.StatusOK, fmt.Sprintf(`<span style="color: %s;">%s</span>`, color, msg))
	})
}
