package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

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

// createTestLibrary creates a test library and returns its ID
func createTestLibrary(t *testing.T, ts *httptest.Server, token, name string) string {
	t.Helper()

	payload := map[string]interface{}{
		"name":        name,
		"description": "Test library for ISBN tests",
		"type":        "ebooks",
	}

	body, _ := json.Marshal(payload)
	req, _ := http.NewRequest("POST", ts.URL+"/api/libraries", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+token)

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

	require.Equal(t, http.StatusCreated, resp.StatusCode)

	var result map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&result)
	require.NoError(t, err)

	return result["id"].(string)
}

// TestMediaItemISBNNormalization tests ISBN normalization with media-items endpoint
func TestMediaItemISBNNormalization(t *testing.T) {
	setup := setupTestServer(t)

	// Create an ebook library first
	token := setup.Token
	libID := createTestLibrary(t, setup.Server, token, "test-isbn-lib")

	// Add a folder to the library (required before adding media items)
	addFolderToLibrary(t, setup, libID, "/app/uploads")

	// Test ISBN normalization cases
	testCases := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "ISBN-13 with hyphens (978-0-12345-678-9)",
			input:    "978-0-12345-678-9",
			expected: "9780123456789",
		},
		{
			name:     "ISBN-13 with single hyphen (978-0123456789)",
			input:    "978-0123456789",
			expected: "9780123456789",
		},
		{
			name:     "ISBN-13 without hyphens",
			input:    "9780123456789",
			expected: "9780123456789",
		},
		{
			name:     "ISBN-13 with spaces",
			input:    "978 0123456789",
			expected: "9780123456789",
		},
		{
			name:     "ISBN-13 with mixed hyphens and spaces",
			input:    "978-0 1234-56789",
			expected: "9780123456789",
		},
		{
			name:     "ISBN-10 with hyphens",
			input:    "0-12345-678-9",
			expected: "9780123456786",
		},
		{
			name:     "ISBN-10 without hyphens",
			input:    "0123456789",
			expected: "9780123456786",
		},
		{
			name:     "ISBN-10 with X",
			input:    "0-12345-678-X",
			expected: "9780123456786",
		},
		{
			name:     "ISBN-10 converts to ISBN-13",
			input:    "0-306-40615-2",
			expected: "9780306406157",
		},
		{
			name:     "ISBN-13 with trailing hyphen",
			input:    "978030640615-7-",
			expected: "9780306406157",
		},
		{
			name:     "ISBN-10 with trailing hyphen",
			input:    "0306406152-",
			expected: "9780306406157",
		},
		{
			name:     "only hyphens converts to empty string",
			input:    "---",
			expected: "",
		},
		{
			name:     "only spaces converts to empty string",
			input:    "   ",
			expected: "",
		},
		{
			name:     "ISBN-10 with X converts to ISBN-13",
			input:    "0-596-00965-X",
			expected: "9780596009656",
		},
		{
			name:     "ISBN-13 with leading/trailing hyphens",
			input:    "-978-0-306-40615-7-",
			expected: "9780306406157",
		},
		{
			name:     "ISBN-13 preserves case",
			input:    "978-0-306-40615-7",
			expected: "9780306406157",
		},
		{
			name:     "ISBN-13 lowercase preserved",
			input:    "978-0-306-40615-7",
			expected: "9780306406157",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			payload := map[string]interface{}{
				"title":      fmt.Sprintf("Test Book %s", tc.name),
				"isbn":       tc.input,
				"library_id": libID,
				"file_path":  "/test/path.epub",
				"file_size":  1024,
				"mime_type":  "application/epub+zip",
			}

			body, _ := json.Marshal(payload)
			req, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items", bytes.NewBuffer(body))
			req.Header.Set("Content-Type", "application/json")
			req.Header.Set("Authorization", "Bearer "+token)

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

			// Check if this is an invalid ISBN case that should return 422
			if tc.expected == "" && (tc.input == "---" || tc.input == "   ") {
				assert.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
			} else {
				assert.Equal(t, http.StatusCreated, resp.StatusCode)
			}

			var response map[string]interface{}
			err = json.NewDecoder(resp.Body).Decode(&response)
			require.NoError(t, err)

			// For valid ISBN responses, verify normalization worked correctly
			if resp.StatusCode == http.StatusCreated {
				assert.Equal(t, tc.expected, response["isbn"])
			}
		})
	}
}

// TestMediaItemISBNEdgeCases tests ISBN edge cases
func TestMediaItemISBNEdgeCases(t *testing.T) {
	setup := setupTestServer(t)

	token := setup.Token
	libID := createTestLibrary(t, setup.Server, token, "test-isbn-edge-lib")
	addFolderToLibrary(t, setup, libID, "/app/uploads")

	t.Run("Empty ISBN should be accepted", func(t *testing.T) {
		payload := map[string]interface{}{
			"title":      "Book without ISBN",
			"library_id": libID,
			"file_path":  "/test/path.epub",
			"file_size":  1024,
			"mime_type":  "application/epub+zip",
		}

		body, _ := json.Marshal(payload)
		req, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items", bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", "Bearer "+token)

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

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

	t.Run("ISBN with multiple hyphens", func(t *testing.T) {
		payload := map[string]interface{}{
			"title":      "Multi-Hyphen ISBN",
			"isbn":       "978-0-306-40615-7",
			"library_id": libID,
			"file_path":  "/test/path.epub",
			"file_size":  1024,
			"mime_type":  "application/epub+zip",
		}

		body, _ := json.Marshal(payload)
		req, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items", bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", "Bearer "+token)

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

		var response map[string]interface{}
		err = json.NewDecoder(resp.Body).Decode(&response)
		require.NoError(t, err)

		assert.Equal(t, http.StatusCreated, resp.StatusCode)
		assert.Equal(t, "9780306406157", response["isbn"])
	})

	t.Run("ISBN with trailing hyphen", func(t *testing.T) {
		payload := map[string]interface{}{
			"title":      "Trailing Hyphen ISBN",
			"isbn":       "978-0-596-00965-2",
			"library_id": libID,
			"file_path":  "/test/path.epub",
			"file_size":  1024,
			"mime_type":  "application/epub+zip",
		}

		body, _ := json.Marshal(payload)
		req, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items", bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", "Bearer "+token)

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

		var response map[string]interface{}
		err = json.NewDecoder(resp.Body).Decode(&response)
		require.NoError(t, err)

		assert.Equal(t, http.StatusCreated, resp.StatusCode)
		assert.Equal(t, "9780596009652", response["isbn"])
	})
}

// TestMediaItemsPagination tests pagination with media-items endpoint
func TestMediaItemsPagination(t *testing.T) {
	setup := setupTestServer(t)

	token := setup.Token
	libID := createTestLibrary(t, setup.Server, token, "test-pagination-lib")
	addFolderToLibrary(t, setup, libID, "/app/uploads")

	// Create some test media items
	for i := 1; i <= 5; i++ {
		payload := map[string]interface{}{
			"title":      fmt.Sprintf("Book %d", i),
			"isbn":       fmt.Sprintf("978030640615%d", i),
			"library_id": libID,
			"file_path":  "/test/path.epub",
			"file_size":  1024,
			"mime_type":  "application/epub+zip",
		}

		body, _ := json.Marshal(payload)
		req, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items", bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", "Bearer "+token)

		client := &http.Client{}
		resp, err := client.Do(req)
		require.NoError(t, err)
		_ = resp.Body.Close()
	}

	// Small delay to allow database to commit before pagination queries
	time.Sleep(100 * time.Millisecond)

	t.Run("Valid pagination parameters", func(t *testing.T) {
		req, _ := http.NewRequest("GET", setup.Server.URL+"/api/media-items?library_id="+libID+"&limit=2&offset=0", nil)
		req.Header.Set("Authorization", "Bearer "+token)

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

		assert.Equal(t, http.StatusOK, resp.StatusCode)

		var response map[string]interface{}
		err = json.NewDecoder(resp.Body).Decode(&response)
		require.NoError(t, err)
		data := response["data"].([]interface{})

		// Should get 2 items
		assert.Equal(t, 2, len(data))
	})

	t.Run("Pagination with offset", func(t *testing.T) {
		req, _ := http.NewRequest("GET", setup.Server.URL+"/api/media-items?library_id="+libID+"&limit=2&offset=2", nil)
		req.Header.Set("Authorization", "Bearer "+token)

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

		assert.Equal(t, http.StatusOK, resp.StatusCode)

		var response map[string]interface{}
		err = json.NewDecoder(resp.Body).Decode(&response)
		require.NoError(t, err)
		data := response["data"].([]interface{})

		// Should get 2 items starting from offset 2
		assert.Equal(t, 2, len(data))
	})

	t.Run("Negative limit should fail", func(t *testing.T) {
		req, _ := http.NewRequest("GET", setup.Server.URL+"/api/media-items?library_id="+libID+"&limit=-10&offset=0", nil)
		req.Header.Set("Authorization", "Bearer "+token)

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

		// Should return 400 Bad Request or handle it gracefully
		assert.NotEqual(t, http.StatusOK, resp.StatusCode)
	})

	t.Run("Negative offset should fail", func(t *testing.T) {
		req, _ := http.NewRequest("GET", setup.Server.URL+"/api/media-items?library_id="+libID+"&limit=10&offset=-5", nil)
		req.Header.Set("Authorization", "Bearer "+token)

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

		// Should return 400 Bad Request or handle it gracefully
		assert.NotEqual(t, http.StatusOK, resp.StatusCode)
	})

	t.Run("Limit exceeds maximum", func(t *testing.T) {
		req, _ := http.NewRequest("GET", setup.Server.URL+"/api/media-items?library_id="+libID+"&limit=10000&offset=0", nil)
		req.Header.Set("Authorization", "Bearer "+token)

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

		// Should be capped at maximum or return error
		// The application uses maxPaginationLimit = 1000
		assert.NotEqual(t, http.StatusInternalServerError, resp.StatusCode)
	})
}

// TestMediaItemLibraryRequirement tests that media items require a library
func TestMediaItemLibraryRequirement(t *testing.T) {
	setup := setupTestServer(t)

	token := setup.Token

	t.Run("Create media-item without library should fail gracefully", func(t *testing.T) {
		payload := map[string]interface{}{
			"title":     "Orphan Book",
			"isbn":      "9780123456789",
			"file_path": "/test/path.epub",
			"file_size": 1024,
			"mime_type": "application/epub+zip",
		}

		body, _ := json.Marshal(payload)
		req, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items", bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", "Bearer "+token)

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

		// Should fail - library_id is required
		assert.NotEqual(t, http.StatusCreated, resp.StatusCode)
	})

	t.Run("Create media-item with existing library should succeed", func(t *testing.T) {
		libID := createTestLibrary(t, setup.Server, token, "test-req-lib")
		addFolderToLibrary(t, setup, libID, "/app/uploads")

		payload := map[string]interface{}{
			"title":      "Valid Book",
			"isbn":       "978-0-306-40615-7",
			"library_id": libID,
			"file_path":  "/test/path.epub",
			"file_size":  1024,
			"mime_type":  "application/epub+zip",
		}

		body, _ := json.Marshal(payload)
		req, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items", bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", "Bearer "+token)

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

		assert.Equal(t, http.StatusCreated, resp.StatusCode)

		var response map[string]interface{}
		err = json.NewDecoder(resp.Body).Decode(&response)
		require.NoError(t, err)

		// VerifyISBN was normalized
		assert.Equal(t, "9780306406157", response["isbn"])
		assert.Equal(t, libID, response["library_id"])
	})
}

// TestUpdateMediaItemISBN tests updating media-item ISBN
func TestUpdateMediaItemISBN(t *testing.T) {
	setup := setupTestServer(t)

	token := setup.Token
	libID := createTestLibrary(t, setup.Server, token, "test-update-lib")
	addFolderToLibrary(t, setup, libID, "/app/uploads")

	// First create a media item
	createPayload := map[string]interface{}{
		"title":      "Original Title",
		"isbn":       "978-0-596-00965-2",
		"library_id": libID,
		"file_path":  "/test/path.epub",
		"file_size":  1024,
		"mime_type":  "application/epub+zip",
	}

	body, _ := json.Marshal(createPayload)
	req, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(t, err)

	var createResponse map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&createResponse)
	require.NoError(t, err)
	_ = resp.Body.Close()

	mediaItemID := createResponse["id"].(string)

	// Now update with new ISBN
	t.Run("Update with invalid ISBN rejects", func(t *testing.T) {
		updatePayload := map[string]interface{}{
			"title": "Updated Title",
			"isbn":  "978-9876543210-9",
		}

		body, _ := json.Marshal(updatePayload)
		req, _ := http.NewRequest("PUT", setup.Server.URL+"/api/media-items/"+mediaItemID, bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", "Bearer "+token)

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

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