package main

import (
	"bytes"
	"encoding/json"
	"io"
	"net/http"
	"testing"

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

// TestBookMatchingQueryBooks tests the book query endpoint
func TestBookMatchingQueryBooks(t *testing.T) {
	setup := setupTestServer(t)

	t.Run("QueryBooks_WithoutAuth", func(t *testing.T) {
		req := map[string]interface{}{
			"title": "Test Book",
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/books/query", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")

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

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

	t.Run("QueryBooks_WithAuth_ByTitle", func(t *testing.T) {
		_ = createTestMediaItemID(t, setup)

		req := map[string]interface{}{
			"title": "Test Ebook",
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/books/query", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

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

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

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

		assert.Contains(t, result, "matches")
		assert.Contains(t, result, "action")
	})

	t.Run("QueryBooks_InvalidRequestBody", func(t *testing.T) {
		// Send invalid JSON
		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/books/query", bytes.NewBuffer([]byte("invalid json")))
		httpReq.Header.Set("Content-Type", "application/json")
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

		client := &http.Client{}
		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("QueryBooks_NoResults", func(t *testing.T) {
		req := map[string]interface{}{
			"title": "NonExistentBookTitleThatDoesNotExist123456789",
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/books/query", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

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

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

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

		var matches []interface{}
		if matchesIf, ok := result["matches"]; ok && matchesIf != nil {
			if matchesSlice, ok := matchesIf.([]interface{}); ok {
				matches = matchesSlice
			}
		}
		assert.Equal(t, 0, len(matches))
	})
}

// TestBookMatchingBulkLink tests bulk linking operations
func TestBookMatchingBulkLink(t *testing.T) {
	setup := setupTestServer(t)

	t.Run("BulkLinkBooks_WithoutAuth", func(t *testing.T) {
		req := map[string]interface{}{
			"links": []map[string]interface{}{
				{
					"unlinked_book_id": uuid.New(),
					"media_item_id":    uuid.New(),
					"confidence_score": 0.9,
				},
			},
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/bulk-link-books", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")

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

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

	t.Run("BulkLinkBooks_WithAuth_EmptyLinks", func(t *testing.T) {
		req := map[string]interface{}{
			"links": []map[string]interface{}{},
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/bulk-link-books", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

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

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

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

		assert.Equal(t, 0.0, result["total"])
		assert.Equal(t, 0.0, result["successful"])
		assert.Equal(t, 0.0, result["failed"])
	})

	t.Run("BulkLinkBooks_InvalidUnlinkedBookID", func(t *testing.T) {
		bookID := createTestMediaItemID(t, setup)

		req := map[string]interface{}{
			"links": []map[string]interface{}{
				{
					"unlinked_book_id": uuid.New(),
					"media_item_id":    bookID,
					"confidence_score": 0.9,
				},
			},
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/bulk-link-books", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

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

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

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

		assert.Contains(t, result, "results")
		assert.Contains(t, result, "total")
		assert.Contains(t, result, "successful")
		assert.Contains(t, result, "failed")

		results := result["results"].([]interface{})
		firstResult := results[0].(map[string]interface{})
		assert.Equal(t, "error", firstResult["status"])
	})

	t.Run("BulkLinkBooks_MultipleLinks", func(t *testing.T) {
		req := map[string]interface{}{
			"links": []map[string]interface{}{
				{
					"unlinked_book_id": uuid.New(),
					"media_item_id":    uuid.New(),
					"confidence_score": 0.9,
				},
				{
					"unlinked_book_id": uuid.New(),
					"media_item_id":    uuid.New(),
					"confidence_score": 0.8,
				},
				{
					"unlinked_book_id": uuid.New(),
					"media_item_id":    uuid.New(),
					"confidence_score": 0.95,
				},
			},
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/bulk-link-books", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

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

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

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

		assert.Equal(t, 3.0, result["total"])
		results := result["results"].([]interface{})
		assert.Equal(t, 3, len(results))
	})
}

// TestBookMatchingAutoLink tests automatic linking
func TestBookMatchingAutoLink(t *testing.T) {
	setup := setupTestServer(t)

	t.Run("AutoLinkBooks_WithoutAuth", func(t *testing.T) {
		req := map[string]interface{}{
			"confidence_threshold": 0.8,
			"limit":                10,
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/auto-link-books", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")

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

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

	t.Run("AutoLinkBooks_WithAuth_DefaultThreshold", func(t *testing.T) {
		req := map[string]interface{}{}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/auto-link-books", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

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

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

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

		assert.Contains(t, result, "auto_linked")
		assert.Contains(t, result, "results")
	})

	t.Run("AutoLinkBooks_CustomThreshold", func(t *testing.T) {
		req := map[string]interface{}{
			"confidence_threshold": 0.95,
			"limit":                20,
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/auto-link-books", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

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

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

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

		assert.Contains(t, result, "auto_linked")
	})

	t.Run("AutoLinkBooks_NoUnlinkedBooks", func(t *testing.T) {
		req := map[string]interface{}{
			"limit": 5,
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/auto-link-books", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

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

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

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

		// Should succeed even with no books to link
		assert.Contains(t, result, "auto_linked")
	})
}

// TestBookMatchingSuggestions tests getting suggestions for unlinked books
func TestBookMatchingSuggestions(t *testing.T) {
	setup := setupTestServer(t)

	t.Run("GetUnlinkedBookSuggestions_WithoutAuth", func(t *testing.T) {
		testID := uuid.New()
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/sync/unlinked-books/"+testID.String()+"/suggestions", nil)

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

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

	t.Run("GetUnlinkedBookSuggestions_InvalidUUID", func(t *testing.T) {
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/sync/unlinked-books/invalid-uuid/suggestions", nil)
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

		client := &http.Client{}
		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("GetUnlinkedBookSuggestions_BookNotFound", func(t *testing.T) {
		testID := uuid.New()
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/sync/unlinked-books/"+testID.String()+"/suggestions", nil)
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

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

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

	t.Run("GetUnlinkedBookSuggestions_ResponseStructure", func(t *testing.T) {
		// Create a test device and unlinked book would go here
		// For now, test with a non-existent ID to check response structure
		testID := uuid.New()
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/sync/unlinked-books/"+testID.String()+"/suggestions", nil)
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

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

		// Even when book not found, we expect 404
		assert.Equal(t, http.StatusNotFound, resp.StatusCode)
	})
}

// TestBookMatchingDeviceFileAliases tests device file alias operations
func TestBookMatchingDeviceFileAliases(t *testing.T) {
	setup := setupTestServer(t)

	t.Run("GetDeviceFileAliases_WithoutAuth", func(t *testing.T) {
		testID := uuid.New()
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/devices/"+testID.String()+"/file-aliases", nil)

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

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

	t.Run("GetDeviceFileAliases_WithAuth", func(t *testing.T) {
		testID := uuid.New()
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/devices/"+testID.String()+"/file-aliases", nil)
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

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

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

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

		assert.Contains(t, result, "device_id")
		assert.Contains(t, result, "aliases")
		assert.Contains(t, result, "total")
	})

	t.Run("CreateDeviceFileAlias_WithoutAuth", func(t *testing.T) {
		deviceID := uuid.New()
		mediaItemID := uuid.New()

		req := map[string]interface{}{
			"media_item_id":    mediaItemID.String(),
			"file_path":        "/mnt/sd/test.epub",
			"file_sha256":      "",
			"confidence_score": 0.9,
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/devices/"+deviceID.String()+"/file-aliases", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")

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

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

	t.Run("CreateDeviceFileAlias_InvalidDeviceID", func(t *testing.T) {
		mediaItemID := uuid.New()

		req := map[string]interface{}{
			"media_item_id":    mediaItemID.String(),
			"file_path":        "/mnt/sd/test.epub",
			"file_sha256":      "",
			"confidence_score": 0.9,
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/devices/invalid-uuid/file-aliases", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

		client := &http.Client{}
		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("CreateDeviceFileAlias_InvalidMediaItemID", func(t *testing.T) {
		deviceID := uuid.New()

		req := map[string]interface{}{
			"media_item_id":    "invalid-uuid",
			"file_path":        "/mnt/sd/test.epub",
			"file_sha256":      "",
			"confidence_score": 0.9,
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/devices/"+deviceID.String()+"/file-aliases", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

		client := &http.Client{}
		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("UpdateDeviceFileAlias_InvalidAliasID", func(t *testing.T) {
		deviceID := uuid.New()

		req := map[string]interface{}{
			"confidence_score": 0.95,
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("PUT", setup.Server.URL+"/api/devices/"+deviceID.String()+"/file-aliases/invalid-uuid", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

		client := &http.Client{}
		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("DeleteDeviceFileAlias_InvalidAliasID", func(t *testing.T) {
		deviceID := uuid.New()

		httpReq, _ := http.NewRequest("DELETE", setup.Server.URL+"/api/devices/"+deviceID.String()+"/file-aliases/invalid-uuid", nil)
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

		client := &http.Client{}
		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)
	})
}

// TestBookMatchingGetBookMatches tests the book matches endpoint
func TestBookMatchingGetBookMatches(t *testing.T) {
	setup := setupTestServer(t)

	t.Run("GetBookMatches_WithoutAuth", func(t *testing.T) {
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/books/match?title=Test", nil)

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

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

	t.Run("GetBookMatches_WithAuth_ByTitle", func(t *testing.T) {
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/books/match?title=Test", nil)
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

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

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

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

		assert.Contains(t, result, "matches")
		assert.Contains(t, result, "action")
	})

	t.Run("GetBookMatches_InvalidFileSize", func(t *testing.T) {
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/books/match?title=Test&file_size=invalid", nil)
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

		client := &http.Client{}
		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("GetBookMatches_MultipleIdentifiers", func(t *testing.T) {
		httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/books/match?identifier=id1&identifier=id2&title=Test", nil)
		httpReq.Header.Set("Authorization", "Bearer "+setup.Token)

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

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

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

		assert.Contains(t, result, "matches")
	})
}
