package handlers

import (
	"bookhoard/internal/database"
	"bookhoard/internal/opds"
	"bookhoard/internal/services"
	"context"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

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

type OPDSHandler struct {
	db                *database.Queries
	conversionService interface {
		ConvertEPUBToKEPUB(ctx context.Context, mediaItemID pgtype.UUID, epubPath string) (*services.ConvertedKEPUB, error)
	}
}

func NewOPDSHandler(db *database.Queries, conversionService interface {
	ConvertEPUBToKEPUB(ctx context.Context, mediaItemID pgtype.UUID, epubPath string) (*services.ConvertedKEPUB, error)
}) *OPDSHandler {
	return &OPDSHandler{
		db:                db,
		conversionService: conversionService,
	}
}

// Helper function to get base URLs from system config
func (h *OPDSHandler) getBaseURLs(c echo.Context) (string, string, error) {
	baseURL, err := h.db.GetSystemConfig(c.Request().Context(), "base_url")
	if err != nil {
		return "", "", fmt.Errorf("failed to get base_url from config: %w", err)
	}

	opdsBaseURL, err := h.db.GetSystemConfig(c.Request().Context(), "opds_base_url")
	if err != nil {
		return "", "", fmt.Errorf("failed to get opds_base_url from config: %w", err)
	}

	return baseURL.Value, opdsBaseURL.Value, nil
}

// GetDeviceCatalog returns the OPDS catalog feed for a device
func (h *OPDSHandler) GetDeviceCatalog(c echo.Context) error {
	deviceID := c.Param("deviceId")

	page := c.QueryParam("page")
	perPage := c.QueryParam("per_page")
	includeFormat := c.QueryParam("include_format")

	// Parse pagination parameters
	pageNum := 1
	if page != "" {
		if num, err := strconv.Atoi(page); err == nil && num > 0 {
			pageNum = num
		}
	}

	perPageNum := 50
	if perPage != "" {
		if num, err := strconv.Atoi(perPage); err == nil && num > 0 && num <= 200 {
			perPageNum = num
		}
	}

	// Get base URLs
	baseURL, opdsBaseURL, err := h.getBaseURLs(c)
	if err != nil {
		return c.XML(http.StatusInternalServerError, opds.NewErrorFeed("Failed to get config"))
	}

	// Parse device ID
	deviceUUID, err := uuid.Parse(deviceID)
	if err != nil {
		return c.XML(http.StatusBadRequest, opds.NewErrorFeed("Invalid device ID"))
	}

	// Verify device exists
	device, err := h.db.GetDevice(c.Request().Context(), pgtype.UUID{Bytes: deviceUUID, Valid: true})
	if err != nil {
		return c.XML(http.StatusNotFound, opds.NewErrorFeed("Device not found"))
	}

	// Get user's visible libraries
	userID := device.UserID.Bytes
	userUUID := uuid.UUID(userID)

	libraries, err := h.db.GetUserVisibleLibraries(c.Request().Context(), pgtype.UUID{Bytes: userID, Valid: true})
	if err != nil {
		return c.XML(http.StatusInternalServerError, opds.NewErrorFeed("Failed to get libraries"))
	}

	// Get media items
	var allItems []database.ListMediaItemsByLibraryRow
	for _, lib := range libraries {
		items, err := h.db.ListMediaItemsByLibrary(c.Request().Context(), pgtype.UUID{Bytes: lib.ID.Bytes, Valid: true})
		if err != nil {
			continue
		}
		allItems = append(allItems, items...)
	}

	// Filter by format if specified
	if includeFormat != "" {
		filteredItems := []database.ListMediaItemsByLibraryRow{}
		for _, item := range allItems {
			if strings.EqualFold(item.FormatGroup, includeFormat) {
				filteredItems = append(filteredItems, item)
			}
		}
		allItems = filteredItems
	}

	// Pagination
	totalItems := len(allItems)
	startIdx := (pageNum - 1) * perPageNum
	endIdx := startIdx + perPageNum

	if startIdx >= totalItems {
		allItems = []database.ListMediaItemsByLibraryRow{}
	} else if endIdx > totalItems {
		allItems = allItems[startIdx:]
	} else {
		allItems = allItems[startIdx:endIdx]
	}

	// Create OPDS feed
	feed := opds.NewFeed(
		fmt.Sprintf("urn:uuid:%s", deviceID),
		"Bookhoard Library",
	)

	// Add feed links
	catalogURL := fmt.Sprintf("%s/opds/devices/%s/catalog", opdsBaseURL, deviceID)
	feed.AddLink(catalogURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "self")
	feed.AddLink(catalogURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "start")

	searchURL := fmt.Sprintf("%s/opds/devices/%s/search", opdsBaseURL, deviceID)
	feed.AddLink(searchURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "search")

	// Add entries
	for _, item := range allItems {
		bookUUID := uuid.UUID(item.ID.Bytes).String()
		title := item.Title
		author := ""
		if item.Author.Valid {
			author = item.Author.String
		}

		updated := item.UpdatedAt.Time.Format("2006-01-02T15:04:05Z")

		entry := opds.NewEntry(
			fmt.Sprintf("urn:uuid:%s", bookUUID),
			title,
			author,
			updated,
		)

		// Add description
		if item.Description.Valid {
			entry.SetSummary(item.Description.String)
		}

		// Add acquisition links
		downloadURL := fmt.Sprintf("%s/opds/devices/%s/download/%s", opdsBaseURL, deviceID, bookUUID)
		entry.AddAcquisitionLink(downloadURL, "application/epub+zip")

		// Add format variants
		kepubURL := fmt.Sprintf("%s?format=kepub", downloadURL)
		entry.AddAlternateLink(kepubURL, "application/vnd.kobo+xml+zip")

		pdfURL := fmt.Sprintf("%s?format=pdf", downloadURL)
		entry.AddAlternateLink(pdfURL, "application/pdf")

		// Add canonical identifier
		entry.SetIdentifier(bookUUID)

		// Add SHA-256 metadata
		if item.FileSha256.Valid {
			entry.AddMetadata("bookhoard:sha256", item.FileSha256.String)
		}

		// Add collections as categories
		collections, err := h.db.GetCollectionsForBook(c.Request().Context(), pgtype.UUID{Bytes: item.ID.Bytes, Valid: true})
		if err == nil {
			collectionScheme := fmt.Sprintf("%s/collections", baseURL)
			for _, col := range collections {
				if col.UserID.Valid && uuid.UUID(col.UserID.Bytes) == userUUID {
					entry.AddCategory(collectionScheme, col.Name)
				}
			}
		}

		feed.AddEntry(entry)
	}

	// Generate XML
	xmlString, err := feed.GenerateXMLString()
	if err != nil {
		return c.XML(http.StatusInternalServerError, opds.NewErrorFeed("Failed to generate feed"))
	}

	c.Response().Header().Set("Content-Type", "application/atom+xml;profile=opds-catalog;kind=acquisition")
	return c.String(http.StatusOK, xmlString)
}

// SearchDeviceCatalog searches the OPDS catalog for a device
func (h *OPDSHandler) SearchDeviceCatalog(c echo.Context) error {
	deviceID := c.Param("deviceId")

	query := c.QueryParam("q")

	if query == "" {
		return c.XML(http.StatusBadRequest, opds.NewErrorFeed("Missing search query"))
	}

	// Get base URLs
	baseURL, opdsBaseURL, err := h.getBaseURLs(c)
	if err != nil {
		return c.XML(http.StatusInternalServerError, opds.NewErrorFeed("Failed to get config"))
	}

	// Parse device ID
	deviceUUID, err := uuid.Parse(deviceID)
	if err != nil {
		return c.XML(http.StatusBadRequest, opds.NewErrorFeed("Invalid device ID"))
	}

	// Verify device exists
	device, err := h.db.GetDevice(c.Request().Context(), pgtype.UUID{Bytes: deviceUUID, Valid: true})
	if err != nil {
		return c.XML(http.StatusNotFound, opds.NewErrorFeed("Device not found"))
	}

	// Get user's visible libraries
	userID := device.UserID.Bytes

	_, err = h.db.GetUserVisibleLibraries(c.Request().Context(), pgtype.UUID{Bytes: userID, Valid: true})
	if err != nil {
		return c.XML(http.StatusInternalServerError, opds.NewErrorFeed("Failed to get libraries"))
	}

	// Search media items
	allItems, err := h.db.SearchMediaItems(c.Request().Context(), database.SearchMediaItemsParams{
		UserID:        pgtype.UUID{Bytes: userID, Valid: true},
		SearchPattern: pgtype.Text{String: "%" + query + "%", Valid: true},
		Offset:        pgtype.Int4{Int32: 0, Valid: true},
		Limit:         pgtype.Int4{Int32: 1000, Valid: true},
	})
	if err != nil {
		allItems = []database.SearchMediaItemsRow{}
	}

	// Create OPDS feed
	feed := opds.NewFeed(
		fmt.Sprintf("urn:uuid:%s", deviceID),
		fmt.Sprintf("Search: %s", query),
	)

	// Add feed links
	catalogURL := fmt.Sprintf("%s/opds/devices/%s/catalog", opdsBaseURL, deviceID)
	feed.AddLink(catalogURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "start")

	searchURL := fmt.Sprintf("%s/opds/devices/%s/search?q=%s", opdsBaseURL, deviceID, query)
	feed.AddLink(searchURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "self")

	// Add entries (same as catalog)
	userUUID := uuid.UUID(userID)
	for _, item := range allItems {
		bookUUID := uuid.UUID(item.ID.Bytes).String()
		title := item.Title
		author := ""
		if item.Author.Valid {
			author = item.Author.String
		}

		updated := item.UpdatedAt.Time.Format("2006-01-02T15:04:05Z")

		entry := opds.NewEntry(
			fmt.Sprintf("urn:uuid:%s", bookUUID),
			title,
			author,
			updated,
		)

		if item.Description.Valid {
			entry.SetSummary(item.Description.String)
		}

		downloadURL := fmt.Sprintf("%s/opds/devices/%s/download/%s", opdsBaseURL, deviceID, bookUUID)
		entry.AddAcquisitionLink(downloadURL, "application/epub+zip")

		kepubURL := fmt.Sprintf("%s?format=kepub", downloadURL)
		entry.AddAlternateLink(kepubURL, "application/vnd.kobo+xml+zip")

		entry.SetIdentifier(bookUUID)

		if item.FileSha256.Valid {
			entry.AddMetadata("bookhoard:sha256", item.FileSha256.String)
		}

		collections, err := h.db.GetCollectionsForBook(c.Request().Context(), pgtype.UUID{Bytes: item.ID.Bytes, Valid: true})
		if err == nil {
			collectionScheme := fmt.Sprintf("%s/collections", baseURL)
			for _, col := range collections {
				if col.UserID.Valid && uuid.UUID(col.UserID.Bytes) == userUUID {
					entry.AddCategory(collectionScheme, col.Name)
				}
			}
		}

		feed.AddEntry(entry)
	}

	xmlString, err := feed.GenerateXMLString()
	if err != nil {
		return c.XML(http.StatusInternalServerError, opds.NewErrorFeed("Failed to generate feed"))
	}

	c.Response().Header().Set("Content-Type", "application/atom+xml;profile=opds-catalog;kind=acquisition")
	return c.String(http.StatusOK, xmlString)
}

// DownloadBook downloads a book with optional format conversion
func (h *OPDSHandler) DownloadBook(c echo.Context) error {
	deviceID := c.Param("deviceId")
	bookID := c.Param("bookId")
	format := c.QueryParam("format") // epub, kepub, pdf, cbz

	// Default to epub
	if format == "" {
		format = "epub"
	}

	// Parse IDs (middleware guarantees valid UUIDs)
	deviceUUID, _ := uuid.Parse(deviceID)
	bookUUID, _ := uuid.Parse(bookID)

	// Verify device exists
	device, err := h.db.GetDevice(c.Request().Context(), pgtype.UUID{Bytes: deviceUUID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "device not found"})
	}

	// Get media item
	mediaItem, err := h.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: bookUUID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "book not found"})
	}

	// Get user's visible libraries
	userID := device.UserID.Bytes
	libraries, err := h.db.GetUserVisibleLibraries(c.Request().Context(), pgtype.UUID{Bytes: userID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to get libraries"})
	}

	// Check if book is in visible library
	visible := false
	for _, lib := range libraries {
		if uuid.UUID(lib.ID.Bytes) == uuid.UUID(mediaItem.LibraryID.Bytes) {
			visible = true
			break
		}
	}

	if !visible {
		return c.JSON(http.StatusForbidden, map[string]string{"error": "book not accessible"})
	}

	// Get format information
	filePath := ""
	var fileSha256 string
	mimeType := ""

	if format == "kepub" {
		kepubFormat, err := h.db.GetMediaItemFormatByType(c.Request().Context(), database.GetMediaItemFormatByTypeParams{
			MediaItemID: pgtype.UUID{Bytes: bookUUID, Valid: true},
			FormatType:  "kepub",
		})

		if err == nil && kepubFormat.FilePath.Valid {
			filePath = kepubFormat.FilePath.String
			if kepubFormat.FileSha256.Valid {
				fileSha256 = kepubFormat.FileSha256.String
			}
			if kepubFormat.MimeType.Valid {
				mimeType = kepubFormat.MimeType.String
			}
		} else {
			if h.conversionService != nil {
				converted, err := h.conversionService.ConvertEPUBToKEPUB(c.Request().Context(), pgtype.UUID{Bytes: bookUUID, Valid: true}, mediaItem.FilePath)
				if err != nil {
					return c.JSON(http.StatusInternalServerError, map[string]string{"error": fmt.Sprintf("KEPUB conversion failed: %v", err)})
				}
				filePath = converted.Path
				fileSha256 = converted.SHA256
				mimeType = "application/vnd.kobo+xml+zip"
			} else {
				filePath = mediaItem.FilePath
				if mediaItem.MimeType.Valid {
					mimeType = mediaItem.MimeType.String
				}
			}
		}
	} else if format == "pdf" {
		// Check for PDF format
		pdfFormat, err := h.db.GetMediaItemFormatByType(c.Request().Context(), database.GetMediaItemFormatByTypeParams{
			MediaItemID: pgtype.UUID{Bytes: bookUUID, Valid: true},
			FormatType:  "pdf",
		})
		if err == nil && pdfFormat.FilePath.Valid {
			filePath = pdfFormat.FilePath.String
			if pdfFormat.FileSha256.Valid {
				fileSha256 = pdfFormat.FileSha256.String
			}
			if pdfFormat.MimeType.Valid {
				mimeType = pdfFormat.MimeType.String
			}
		} else {
			return c.JSON(http.StatusNotFound, map[string]string{"error": "pdf format not available"})
		}
	} else {
		// Default: EPUB
		filePath = mediaItem.FilePath
		if mediaItem.MimeType.Valid {
			mimeType = mediaItem.MimeType.String
		}
	}

	// Check if file exists
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "file not found"})
	}

	// Open file
	file, err := os.Open(filePath)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to open file"})
	}
	defer file.Close()

	// Get file info
	fileInfo, err := file.Stat()
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to get file info"})
	}

	// Set headers
	filename := filepath.Base(filePath)
	c.Response().Header().Set("Content-Type", mimeType)
	c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
	c.Response().Header().Set("X-Bookhoard-UUID", bookUUID.String())

	if format == "kepub" && fileSha256 != "" {
		c.Response().Header().Set("X-Bookhoard-KEPUB-SHA256", fileSha256)
	} else if fileSha256 != "" {
		c.Response().Header().Set("X-Bookhoard-SHA256", fileSha256)
	}

	// Stream file
	http.ServeContent(c.Response(), c.Request(), filename, fileInfo.ModTime(), file)
	return nil
}

// GetCoverImage serves a book's cover image
func (h *OPDSHandler) GetCoverImage(c echo.Context) error {
	deviceID := c.Param("deviceId")
	bookID := c.Param("bookId")

	// Parse IDs (middleware guarantees valid UUIDs)
	deviceUUID, _ := uuid.Parse(deviceID)
	bookUUID, _ := uuid.Parse(bookID)

	// Verify device exists
	device, err := h.db.GetDevice(c.Request().Context(), pgtype.UUID{Bytes: deviceUUID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "device not found"})
	}

	// Get media item
	mediaItem, err := h.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: bookUUID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "book not found"})
	}

	// Get user's visible libraries
	userID := device.UserID.Bytes
	libraries, err := h.db.GetUserVisibleLibraries(c.Request().Context(), pgtype.UUID{Bytes: userID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to get libraries"})
	}

	// Check if book is in visible library
	visible := false
	for _, lib := range libraries {
		if uuid.UUID(lib.ID.Bytes) == uuid.UUID(mediaItem.LibraryID.Bytes) {
			visible = true
			break
		}
	}

	if !visible {
		return c.JSON(http.StatusForbidden, map[string]string{"error": "book not accessible"})
	}

	// Check if cover exists
	if !mediaItem.CoverImagePath.Valid || mediaItem.CoverImagePath.String == "" {
		return c.NoContent(http.StatusNoContent)
	}

	coverPath := mediaItem.CoverImagePath.String

	// Check if file exists
	if _, err := os.Stat(coverPath); os.IsNotExist(err) {
		return c.NoContent(http.StatusNoContent)
	}

	// Open file
	file, err := os.Open(coverPath)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to open cover"})
	}
	defer file.Close()

	// Get file info
	fileInfo, err := file.Stat()
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to get file info"})
	}

	// Determine content type
	ext := strings.ToLower(filepath.Ext(coverPath))
	contentType := "image/jpeg"
	if ext == ".png" {
		contentType = "image/png"
	} else if ext == ".webp" {
		contentType = "image/webp"
	}

	// Set cache headers
	c.Response().Header().Set("Content-Type", contentType)
	c.Response().Header().Set("Cache-Control", "public, max-age=31536000")

	// Stream image
	http.ServeContent(c.Response(), c.Request(), "", fileInfo.ModTime(), file)
	return nil
}

// GetDeviceNavigation returns the OPDS navigation feed for a device
func (h *OPDSHandler) GetDeviceNavigation(c echo.Context) error {
	deviceID := c.Param("deviceId")

	// Get base URLs
	_, opdsBaseURL, err := h.getBaseURLs(c)
	if err != nil {
		return c.XML(http.StatusInternalServerError, opds.NewErrorFeed("Failed to get config"))
	}

	// Parse device ID (middleware guarantees valid UUID)
	deviceUUID, _ := uuid.Parse(deviceID)

	// Verify device exists
	_, err = h.db.GetDevice(c.Request().Context(), pgtype.UUID{Bytes: deviceUUID, Valid: true})
	if err != nil {
		return c.XML(http.StatusNotFound, opds.NewErrorFeed("Device not found"))
	}

	// Create OPDS navigation feed
	feed := opds.NewFeed(
		fmt.Sprintf("urn:uuid:%s", deviceID),
		"Bookhoard Library Navigation",
	)

	// Add feed links
	catalogURL := fmt.Sprintf("%s/opds/devices/%s/catalog", opdsBaseURL, deviceID)
	feed.AddLink(catalogURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "start")
	feed.AddLink(catalogURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "self")

	// Add navigation entry for catalog
	catalogEntry := opds.NewEntry(
		fmt.Sprintf("urn:uuid:%s-catalog", deviceID),
		"All Books",
		"",
		feed.Updated,
	)
	catalogEntry.AddLink(catalogURL, "application/atom+xml;profile=opds-catalog;kind=acquisition", "subsection")
	catalogEntry.SetSummary("Browse all books in the library")
	feed.AddEntry(catalogEntry)

	// Generate XML
	xmlString, err := feed.GenerateXMLString()
	if err != nil {
		return c.XML(http.StatusInternalServerError, opds.NewErrorFeed("Failed to generate feed"))
	}

	c.Response().Header().Set("Content-Type", "application/atom+xml;profile=opds-catalog;kind=navigation")
	return c.String(http.StatusOK, xmlString)
}

// ListFormats lists available formats for a book
func (h *OPDSHandler) ListFormats(c echo.Context) error {
	deviceID := c.Param("deviceId")
	bookID := c.Param("bookId")

	// Parse IDs (middleware guarantees valid UUIDs)
	deviceUUID, _ := uuid.Parse(deviceID)
	bookUUID, _ := uuid.Parse(bookID)

	// Verify device exists
	device, err := h.db.GetDevice(c.Request().Context(), pgtype.UUID{Bytes: deviceUUID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "device not found"})
	}

	// Get media item
	mediaItem, err := h.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: bookUUID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "book not found"})
	}

	// Get user's visible libraries
	userID := device.UserID.Bytes
	libraries, err := h.db.GetUserVisibleLibraries(c.Request().Context(), pgtype.UUID{Bytes: userID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to get libraries"})
	}

	// Check if book is in visible library
	visible := false
	for _, lib := range libraries {
		if uuid.UUID(lib.ID.Bytes) == uuid.UUID(mediaItem.LibraryID.Bytes) {
			visible = true
			break
		}
	}

	if !visible {
		return c.JSON(http.StatusForbidden, map[string]string{"error": "book not accessible"})
	}

	// Get all formats
	formats, err := h.db.GetMediaItemFormats(c.Request().Context(), pgtype.UUID{Bytes: bookUUID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to get formats"})
	}

	type FormatInfo struct {
		FormatType string `json:"format_type"`
		FilePath   string `json:"file_path,omitempty"`
		FileSha256 string `json:"file_sha256,omitempty"`
		FileSize   int64  `json:"file_size_bytes,omitempty"`
		MimeType   string `json:"mime_type,omitempty"`
		Available  bool   `json:"available"`
	}

	formatList := []FormatInfo{}

	// Add EPUB format (always available if media item exists)
	fileSize := int64(0)
	if mediaItem.FileSize.Valid {
		fileSize = mediaItem.FileSize.Int64
	}

	formatList = append(formatList, FormatInfo{
		FormatType: "epub",
		FilePath:   mediaItem.FilePath,
		FileSha256: func() string {
			if mediaItem.FileSha256.Valid {
				return mediaItem.FileSha256.String
			} else {
				return ""
			}
		}(),
		FileSize: fileSize,
		MimeType: func() string {
			if mediaItem.MimeType.Valid {
				return mediaItem.MimeType.String
			} else {
				return "application/epub+zip"
			}
		}(),
		Available: true,
	})

	// Add other formats
	for _, format := range formats {
		fileSize := int64(0)
		if format.FileSizeBytes.Valid {
			fileSize = format.FileSizeBytes.Int64
		}

		formatList = append(formatList, FormatInfo{
			FormatType: format.FormatType,
			FilePath: func() string {
				if format.FilePath.Valid {
					return format.FilePath.String
				} else {
					return ""
				}
			}(),
			FileSha256: func() string {
				if format.FileSha256.Valid {
					return format.FileSha256.String
				} else {
					return ""
				}
			}(),
			FileSize: fileSize,
			MimeType: func() string {
				if format.MimeType.Valid {
					return format.MimeType.String
				} else {
					return ""
				}
			}(),
			Available: format.FilePath.Valid,
		})
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"media_item_id": bookUUID.String(),
		"formats":       formatList,
	})
}

// RegisterOPDS registers a device for OPDS access
func (h *OPDSHandler) RegisterOPDS(c echo.Context) error {
	deviceID := c.Param("deviceId")

	// Parse device ID
	deviceUUID, err := uuid.Parse(deviceID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid device id"})
	}

	// Verify device exists
	_, err = h.db.GetDevice(c.Request().Context(), pgtype.UUID{Bytes: deviceUUID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "device not found"})
	}

	// Get base URLs
	_, opdsBaseURL, err := h.getBaseURLs(c)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to get config"})
	}

	// Generate OPDS token
	tokenBytes := make([]byte, 32)
	if _, err := rand.Read(tokenBytes); err != nil {
		tokenBytes = []byte(deviceID + "-" + uuid.New().String())
	}
	token := hex.EncodeToString(tokenBytes)[:64]

	// Create OPDS token record (valid for 1 year)
	expiresAt := time.Now().AddDate(1, 0, 0)
	_, err = h.db.CreateOpdsToken(c.Request().Context(), database.CreateOpdsTokenParams{
		DeviceID:  pgtype.UUID{Bytes: deviceUUID, Valid: true},
		Token:     token,
		TokenType: pgtype.Text{String: "device", Valid: true},
		ExpiresAt: pgtype.Timestamptz{Time: expiresAt, Valid: true},
	})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to create OPDS token"})
	}

	catalogURL := fmt.Sprintf("%s/opds/devices/%s/catalog", opdsBaseURL, deviceID)

	return c.JSON(http.StatusOK, map[string]interface{}{
		"opds_token": map[string]interface{}{
			"token":      token,
			"token_type": "device",
			"expires_at": expiresAt.Format(time.RFC3339),
		},
		"opds_catalog_url": catalogURL,
		"refresh_interval": 3600,
	})
}
