package main

import (
	"io"
	"net/http"
	"testing"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// TestOPDSEndpoints tests OPDS (Open Publication Distribution System) endpoints
func TestOPDSEndpoints(t *testing.T) {
	setup := setupTestServer(t)
	// Create ALL media items needed for ALL subtests BEFORE any t.Run (following Kobo pattern)
	_ = createTestMediaItemID(t, setup)
	bookID1 := createTestMediaItemID(t, setup)
	bookID2 := createTestMediaItemID(t, setup)
	bookID3 := createTestMediaItemID(t, setup)
	client := &http.Client{}

	t.Run("GetDeviceCatalog_WithoutDeviceAuth", func(t *testing.T) {
		deviceID := uuid.New()
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+deviceID.String()+"/catalog", nil)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		// OPDS endpoints require device authentication via devices.auth_token
		assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
	})

	t.Run("GetDeviceCatalog_InvalidDeviceID", func(t *testing.T) {
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/invalid-uuid/catalog", nil)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		// Should return 400 for invalid UUID
		assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
	})

	t.Run("GetDeviceCatalog_ValidDevice_BearerToken", func(t *testing.T) {
		// Create a device with auth token
		deviceSetup := setupDeviceTest(t)
		device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-bearer-test")

		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/catalog", nil)
		httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		// Should return 200 with catalog (even if empty)
		assert.Equal(t, http.StatusOK, resp.StatusCode)
	})

	t.Run("GetDeviceCatalog_ValidDevice_QueryToken", func(t *testing.T) {
		// Create a device with auth token
		deviceSetup := setupDeviceTest(t)
		device := deviceSetup.CreateDevice(t, "Test OPDS Device", "kobo", "opds-query-test")

		// Test query parameter authentication
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/catalog?token="+device.AuthToken, nil)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		// Should return 200 with catalog (even if empty)
		assert.Equal(t, http.StatusOK, resp.StatusCode)
	})

	t.Run("SearchDeviceCatalog_InvalidDeviceID", func(t *testing.T) {
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/invalid-uuid/search?q=test", nil)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
	})

	t.Run("SearchDeviceCatalog_ValidDevice", func(t *testing.T) {
		// Create a device with auth token
		deviceSetup := setupDeviceTest(t)
		device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-search-test")

		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/search?q=test", nil)
		httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		// Should return 200 (even if empty results)
		assert.Equal(t, http.StatusOK, resp.StatusCode)
	})

	t.Run("GetDeviceNavigation_InvalidDeviceID", func(t *testing.T) {
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/invalid-uuid/nav", nil)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
	})

	t.Run("GetDeviceNavigation_ValidDevice", func(t *testing.T) {
		deviceSetup := setupDeviceTest(t)
		device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-nav-test")

		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/nav", nil)
		httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		// Should return navigation or 404
		assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound)
	})

	t.Run("DownloadBook_InvalidDeviceID", func(t *testing.T) {
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/invalid-uuid/download/"+uuid.New().String(), nil)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
	})

	t.Run("DownloadBook_InvalidBookID", func(t *testing.T) {
		deviceID := uuid.New()
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+deviceID.String()+"/download/invalid-uuid", nil)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
	})

	t.Run("DownloadBook_ValidIDs", func(t *testing.T) {
		deviceSetup := setupDeviceTest(t)
		device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-download-test")

		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/download/"+bookID1, nil)
		httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		// May return 404 if device/book not linked, or 500 for file not found
		// Should not return 400 (invalid IDs)
		assert.NotEqual(t, http.StatusBadRequest, resp.StatusCode)
	})

	t.Run("GetCoverImage_InvalidDeviceID", func(t *testing.T) {
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/invalid-uuid/cover/"+uuid.New().String(), nil)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
	})

	t.Run("GetCoverImage_InvalidBookID", func(t *testing.T) {
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/invalid-uuid/cover/"+uuid.New().String(), nil)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
	})

	t.Run("GetCoverImage_ValidIDs", func(t *testing.T) {
		deviceSetup := setupDeviceTest(t)
		device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-cover-test")

		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/cover/"+bookID2, nil)
		httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		// May return 404 if no cover, but not 400
		assert.NotEqual(t, http.StatusBadRequest, resp.StatusCode)
	})

	t.Run("ListFormats_InvalidDeviceID", func(t *testing.T) {
		bookID := uuid.New()
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/invalid-uuid/formats/"+bookID.String(), nil)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
	})

	t.Run("ListFormats_ValidDeviceID", func(t *testing.T) {
		deviceSetup := setupDeviceTest(t)
		device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-formats-test")

		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/formats/"+bookID3, nil)
		httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		// Should return formats list or 404
		assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound)
	})
}

// TestOPDSConversion tests on-the-fly conversion for downloads
func TestOPDSConversion(t *testing.T) {
	setup := setupTestServer(t)
	// Create ALL media items needed for ALL subtests BEFORE any t.Run (following Kobo pattern)
	_ = createTestMediaItemID(t, setup)
	bookID1 := createTestMediaItemID(t, setup)
	bookID2 := createTestMediaItemID(t, setup)
	bookID3 := createTestMediaItemID(t, setup)
	client := &http.Client{}

	t.Run("DownloadKEPUB_FormatParameter", func(t *testing.T) {
		deviceSetup := setupDeviceTest(t)
		device := deviceSetup.CreateDevice(t, "Test OPDS Device", "kobo", "opds-kepub-test")

		// Request KEPUB format
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/download/"+bookID1+"?format=kepub", nil)
		httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		// Should attempt conversion (may fail if file doesn't exist)
		// Important: Should not return 400 for invalid IDs
		assert.NotEqual(t, http.StatusBadRequest, resp.StatusCode)
	})

	t.Run("DownloadEPUB_DefaultFormat", func(t *testing.T) {
		deviceSetup := setupDeviceTest(t)
		device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-epub-test")

		// Request default format (no format parameter)
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/download/"+bookID2, nil)
		httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		// Should attempt to download original format
		assert.NotEqual(t, http.StatusBadRequest, resp.StatusCode)
	})

	t.Run("Download_UnsupportedFormat", func(t *testing.T) {
		deviceSetup := setupDeviceTest(t)
		device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-unsupported-test")

		// Request unsupported format
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/download/"+bookID3+"?format=pdf", nil)
		httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		// Should handle gracefully (either 400 for unsupported format or 404/500)
		assert.True(t, resp.StatusCode >= 400 && resp.StatusCode < 600)
	})
}

// TestOPDSEdgeCases tests edge cases for OPDS endpoints
func TestOPDSEdgeCases(t *testing.T) {
	setup := setupTestServer(t)
	_ = createTestMediaItemID(t, setup)
	client := &http.Client{}

	t.Run("Catalog_EmptyLibrary", func(t *testing.T) {
		deviceSetup := setupDeviceTest(t)
		device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-edge-empty")

		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/catalog", nil)
		httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		// Should return empty catalog, not error
		assert.Equal(t, http.StatusOK, resp.StatusCode)
	})

	t.Run("Search_SpecialCharacters", func(t *testing.T) {
		deviceSetup := setupDeviceTest(t)
		device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-edge-special")

		// Search with special characters
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/search?q=test%20%26%20more", nil)
		httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		// Should handle special characters
		assert.Equal(t, http.StatusOK, resp.StatusCode)
	})

	t.Run("Search_EmptyQuery", func(t *testing.T) {
		deviceSetup := setupDeviceTest(t)
		device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-edge-emptyq")

		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/opds/devices/"+device.ID.String()+"/search?q=", nil)
		httpReq.Header.Set("Authorization", "Bearer "+device.AuthToken)

		resp, err := client.Do(httpReq)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(resp.Body)

		// Should handle empty query
		assert.True(t, resp.StatusCode >= 200 && resp.StatusCode < 500)
	})
}

// TestOPDSSearchAcrossLibraries - verify OPDS actually searches across all libraries
func TestOPDSSearchAcrossLibraries(t *testing.T) {
	setup := setupTestServer(t)
	client := &http.Client{}

	// Create two libraries
	lib1Resp := createLibrary(t, client, setup, "OPDS Test Lib 1")
	lib2Resp := createLibrary(t, client, setup, "OPDS Test Lib 2")

	// Add folders
	addFolderToLibrary(t, setup, lib1Resp["id"].(string), "/app/uploads")
	addFolderToLibrary(t, setup, lib2Resp["id"].(string), "/app/uploads")

	// Add books to each library
	book1ID := createTestMediaItemIDInLibrary(t, client, setup, lib1Resp["id"].(string), "OPDS Book 1")
	book2ID := createTestMediaItemIDInLibrary(t, client, setup, lib2Resp["id"].(string), "OPDS Book 2")

	t.Logf("Created book1 in lib1: %s", book1ID)
	t.Logf("Created book2 in lib2: %s", book2ID)

	// Create device for OPDS access
	deviceSetup := setupDeviceTest(t)
	device := deviceSetup.CreateDevice(t, "Test OPDS Device", "koreader", "opds-cross-lib-test")

	// Search via OPDS (no library_id parameter)
	searchURL := setup.Server.URL + "/opds/devices/" + device.ID.String() + "/search?q=OPDS"
	t.Logf("OPDS Search URL: %s", searchURL)

	req, _ := http.NewRequest("GET", searchURL, nil)
	req.Header.Set("Authorization", "Bearer "+device.AuthToken)

	resp, err := client.Do(req)
	require.NoError(t, err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	t.Logf("OPDS Search Status: %d", resp.StatusCode)

	// Read response body
	bodyBytes, _ := io.ReadAll(resp.Body)
	bodyString := string(bodyBytes)

	t.Logf("OPDS response length: %d bytes", len(bodyString))

	if resp.StatusCode == 200 {
		t.Logf("✅ SUCCESS - OPDS search returns 200 (not 404 like SearchMediaItemsUnified)")
		t.Logf("   Response contains 'OPDS Book 1': %v", contains(bodyString, "OPDS Book 1"))
		t.Logf("   Response contains 'OPDS Book 2': %v", contains(bodyString, "OPDS Book 2"))
	} else {
		t.Logf("❌ FAILED - OPDS returned %d", resp.StatusCode)
	}
}
