package handlers

import (
	"bookhoard/internal/database"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

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

type SidecarHandler struct {
	db *database.Queries
}

func NewSidecarHandler(db *database.Queries) *SidecarHandler {
	return &SidecarHandler{db: db}
}

type SidecarConfig struct {
	Version        string                 `json:"version"`
	Bookhoard      SidecarBookhoardConfig `json:"bookhoard"`
	Books          map[string]SidecarBook `json:"books"`
	Collections    []SidecarCollection    `json:"collections"`
	OPDSEnabled    bool                   `json:"opds_enabled"`
	SidecarEnabled bool                   `json:"sidecar_enabled"`
	LastUpdated    string                 `json:"last_updated"`
}

type SidecarBookhoardConfig struct {
	OPDSCatalog string `json:"opds_catalog"`
	SyncAPI     string `json:"sync_api"`
	OPDSBaseURL string `json:"opds_base_url"`
	APIBaseURL  string `json:"api_base_url"`
	DeviceID    string `json:"device_id"`
	DeviceToken string `json:"device_token,omitempty"`
}

type SidecarBook struct {
	BookhoardUUID    string   `json:"bookhoard_uuid"`
	Title            string   `json:"title"`
	Author           string   `json:"author"`
	AvailableFormats []string `json:"available_formats"`
	SHA256           string   `json:"sha256,omitempty"`
	FilePath         string   `json:"file_path,omitempty"`
}

type SidecarCollection struct {
	Name         string   `json:"name"`
	ShelfMapping string   `json:"shelf_mapping,omitempty"`
	BookIDs      []string `json:"book_ids"`
}

// GetSidecarConfig generates and returns sidecar configuration for a device
// GET /api/devices/:device_id/sidecar
func (h *SidecarHandler) GetSidecarConfig(c echo.Context) error {
	deviceID, err := uuid.Parse(c.Param("device_id"))
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"error": "invalid device ID",
		})
	}

	ctx := c.Request().Context()
	pgDeviceID := pgtype.UUID{Bytes: deviceID, Valid: true}

	// Get device info
	device, err := h.db.GetDevice(ctx, pgDeviceID)
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{
			"error": "device not found",
		})
	}

	// Get user info
	userID := device.UserID.Bytes
	pgUserID := pgtype.UUID{Bytes: userID, Valid: true}

	// Get system config
	opdsBaseURL, _ := h.db.GetSystemConfig(ctx, "opds_base_url")
	apiBaseURL, _ := h.db.GetSystemConfig(ctx, "api_base_url")

	// Generate URLs
	opdsCatalogURL := fmt.Sprintf("%s/opds/devices/%s/catalog", opdsBaseURL.Value, deviceID.String())
	syncAPIURL := fmt.Sprintf("%s/sync/kobo", apiBaseURL.Value)

	// Get user's visible libraries with media items
	mediaItems, err := h.db.GetUserMediaItemsForSync(ctx, pgUserID)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{
			"error": "failed to fetch media items",
		})
	}

	// Build books map (keyed by SHA256, fallback to UUID)
	books := make(map[string]SidecarBook)
	for _, item := range mediaItems {
		bookUUID := uuid.UUID(item.ID.Bytes).String()

		// Use SHA256 as key if available, otherwise use UUID
		key := bookUUID
		if item.FileSha256.Valid && item.FileSha256.String != "" {
			key = item.FileSha256.String
		}

		availableFormats := []string{"epub"}
		if item.MimeType.Valid {
			if item.MimeType.String == "application/epub+zip" || item.MimeType.String == "application/octet-stream" {
				availableFormats = append(availableFormats, "kepub")
			}
		}

		author := ""
		if item.Author.Valid {
			author = item.Author.String
		}

		books[key] = SidecarBook{
			BookhoardUUID:    bookUUID,
			Title:            item.Title,
			Author:           author,
			AvailableFormats: availableFormats,
			SHA256:           item.FileSha256.String,
			FilePath:         item.FilePath,
		}
	}

	// Get collections
	collections, err := h.db.GetCollectionsByUser(ctx, pgUserID)
	if err != nil {
		// Non-fatal error, continue with empty collections
		collections = []database.Collections{}
	}

	// Build collections array
	sidecarCollections := []SidecarCollection{}
	for _, collection := range collections {
		// Get collection items
		collectionItems, err := h.db.GetCollectionItems(ctx, collection.ID)
		if err != nil {
			continue
		}

		bookIDs := make([]string, len(collectionItems))
		for i, item := range collectionItems {
			bookIDs[i] = uuid.UUID(item.MediaItemID.Bytes).String()
		}

		// Check for device shelf mapping
		shelfMapping := collection.Name
		mapping, err := h.db.GetDeviceShelfMapping(ctx, database.GetDeviceShelfMappingParams{
			DeviceID:     pgDeviceID,
			CollectionID: pgtype.UUID{Bytes: collection.ID.Bytes, Valid: true},
		})
		if err == nil && mapping.DeviceShelfName.Valid {
			shelfMapping = mapping.DeviceShelfName.String
		}

		sidecarCollections = append(sidecarCollections, SidecarCollection{
			Name:         collection.Name,
			ShelfMapping: shelfMapping,
			BookIDs:      bookIDs,
		})
	}

	// Build sidecar config
	config := SidecarConfig{
		Version: "1.0",
		Bookhoard: SidecarBookhoardConfig{
			OPDSCatalog: opdsCatalogURL,
			SyncAPI:     syncAPIURL,
			OPDSBaseURL: opdsBaseURL.Value,
			APIBaseURL:  apiBaseURL.Value,
			DeviceID:    deviceID.String(),
			DeviceToken: device.AuthToken,
		},
		Books:          books,
		Collections:    sidecarCollections,
		OPDSEnabled:    true,
		SidecarEnabled: true,
		LastUpdated:    time.Now().Format(time.RFC3339),
	}

	return c.JSON(http.StatusOK, config)
}

// DownloadSidecarConfig generates a .bookhoard.json file for device setup
// GET /api/devices/:device_id/sidecar/download
func (h *SidecarHandler) DownloadSidecarConfig(c echo.Context) error {
	deviceID, err := uuid.Parse(c.Param("device_id"))
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"error": "invalid device ID",
		})
	}

	ctx := c.Request().Context()
	pgDeviceID := pgtype.UUID{Bytes: deviceID, Valid: true}

	// Get device info to get device name
	device, err := h.db.GetDevice(ctx, pgDeviceID)
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{
			"error": "device not found",
		})
	}

	// Generate sidecar config
	var sidecarConfig SidecarConfig

	// Reuse GetSidecarConfig logic by building config inline
	userID := device.UserID.Bytes
	pgUserID := pgtype.UUID{Bytes: userID, Valid: true}

	// Get system config
	opdsBaseURL, _ := h.db.GetSystemConfig(ctx, "opds_base_url")
	apiBaseURL, _ := h.db.GetSystemConfig(ctx, "api_base_url")

	// Generate URLs
	opdsCatalogURL := fmt.Sprintf("%s/opds/devices/%s/catalog", opdsBaseURL.Value, deviceID.String())
	syncAPIURL := fmt.Sprintf("%s/sync/kobo", apiBaseURL.Value)

	// Get user's visible libraries with media items
	mediaItems, err := h.db.GetUserMediaItemsForSync(ctx, pgUserID)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{
			"error": "failed to fetch media items",
		})
	}

	// Build books map
	books := make(map[string]SidecarBook)
	for _, item := range mediaItems {
		bookUUID := uuid.UUID(item.ID.Bytes).String()

		key := bookUUID
		if item.FileSha256.Valid && item.FileSha256.String != "" {
			key = item.FileSha256.String
		}

		availableFormats := []string{"epub"}
		if item.MimeType.Valid {
			if item.MimeType.String == "application/epub+zip" || item.MimeType.String == "application/octet-stream" {
				availableFormats = append(availableFormats, "kepub")
			}
		}

		author := ""
		if item.Author.Valid {
			author = item.Author.String
		}

		books[key] = SidecarBook{
			BookhoardUUID:    bookUUID,
			Title:            item.Title,
			Author:           author,
			AvailableFormats: availableFormats,
			SHA256:           item.FileSha256.String,
			FilePath:         item.FilePath,
		}
	}

	// Get collections
	collections, err := h.db.GetCollectionsByUser(ctx, pgUserID)
	if err != nil {
		collections = []database.Collections{}
	}

	// Build collections array
	sidecarCollections := []SidecarCollection{}
	for _, collection := range collections {
		collectionItems, err := h.db.GetCollectionItems(ctx, collection.ID)
		if err != nil {
			continue
		}

		bookIDs := make([]string, len(collectionItems))
		for i, item := range collectionItems {
			bookIDs[i] = uuid.UUID(item.MediaItemID.Bytes).String()
		}

		shelfMapping := collection.Name
		mapping, err := h.db.GetDeviceShelfMapping(ctx, database.GetDeviceShelfMappingParams{
			DeviceID:     pgDeviceID,
			CollectionID: pgtype.UUID{Bytes: collection.ID.Bytes, Valid: true},
		})
		if err == nil && mapping.DeviceShelfName.Valid {
			shelfMapping = mapping.DeviceShelfName.String
		}

		sidecarCollections = append(sidecarCollections, SidecarCollection{
			Name:         collection.Name,
			ShelfMapping: shelfMapping,
			BookIDs:      bookIDs,
		})
	}

	sidecarConfig = SidecarConfig{
		Version: "1.0",
		Bookhoard: SidecarBookhoardConfig{
			OPDSCatalog: opdsCatalogURL,
			SyncAPI:     syncAPIURL,
			OPDSBaseURL: opdsBaseURL.Value,
			APIBaseURL:  apiBaseURL.Value,
			DeviceID:    deviceID.String(),
			DeviceToken: device.AuthToken,
		},
		Books:          books,
		Collections:    sidecarCollections,
		OPDSEnabled:    true,
		SidecarEnabled: true,
		LastUpdated:    time.Now().Format(time.RFC3339),
	}

	// Marshal to JSON with pretty formatting
	configJSON, err := json.MarshalIndent(sidecarConfig, "", "  ")
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{
			"error": "failed to generate config",
		})
	}

	// Set headers for file download
	filename := fmt.Sprintf("%s.bookhoard.json", sanitizeFilename(device.DeviceName))
	c.Response().Header().Set("Content-Type", "application/json")
	c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))

	return c.JSONBlob(http.StatusOK, configJSON)
}

// GetSystemConfiguration returns system-wide configuration
// GET /api/system/config
func (h *SidecarHandler) GetSystemConfiguration(c echo.Context) error {
	ctx := c.Request().Context()

	// Get all system config
	configs, err := h.db.GetAllSystemConfig(ctx)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{
			"error": "failed to fetch system config",
		})
	}

	// Build config map
	result := make(map[string]string)
	for _, config := range configs {
		result[config.Key] = config.Value
	}

	return c.JSON(http.StatusOK, result)
}

// UpdateSystemConfiguration updates system-wide configuration
// PUT /api/system/config
func (h *SidecarHandler) UpdateSystemConfiguration(c echo.Context) error {
	user := c.Get("user")
	if user == nil {
		return c.JSON(http.StatusUnauthorized, map[string]string{
			"error": "unauthorized",
		})
	}

	userInfo := user.(database.Users)
	if userInfo.Role != "admin" {
		return c.JSON(http.StatusForbidden, map[string]string{
			"error": "admin access required",
		})
	}

	var req map[string]string
	if err := c.Bind(&req); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"error": "invalid request format",
		})
	}

	ctx := c.Request().Context()
	pgUserID := pgtype.UUID{Bytes: userInfo.ID.Bytes, Valid: true}

	// Update each config value
	for key, value := range req {
		_, err := h.db.SetSystemConfig(ctx, database.SetSystemConfigParams{
			Key:       key,
			Value:     value,
			UpdatedBy: pgUserID,
		})
		if err != nil {
			return c.JSON(http.StatusInternalServerError, map[string]string{
				"error": fmt.Sprintf("failed to update config key: %s", key),
			})
		}
	}

	return c.JSON(http.StatusOK, map[string]string{
		"status":  "success",
		"message": "System configuration updated",
	})
}

func sanitizeFilename(name string) string {
	// Simple sanitization - replace problematic characters
	sanitized := name
	for _, ch := range []string{"/", "\\", ":", "*", "?", "\"", "<", ">", "|"} {
		sanitized = sanitizeAll(sanitized, ch, "_")
	}
	return sanitized
}

func sanitizeAll(s string, old string, new string) string {
	result := ""
	for _, ch := range s {
		c := string(ch)
		if c == old {
			result += new
		} else {
			result += c
		}
	}
	return result
}
