package main

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

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

func TestConflictDetection_TriggeringConditions(t *testing.T) {
	t.Run("conflict detected when different devices sync within 5 minutes", func(t *testing.T) {
		conflictData := map[string]map[string]interface{}{
			"koreader": {
				"source":    "koreader",
				"timestamp": "2026-01-30T20:10:00Z",
				"data": map[string]interface{}{
					"percentage": 0.45,
					"epubcfi":    "epubcfi(/6/4/2:15)",
					"chapter":    3,
				},
			},
			"kobo": {
				"source":    "kobo",
				"timestamp": "2026-01-30T20:05:00Z",
				"data": map[string]interface{}{
					"percentage": 0.42,
					"page":       89,
				},
			},
		}

		body, err := json.Marshal(conflictData)
		assert.NoError(t, err)

		req := httptest.NewRequest("POST", "/api/sync/koreader/progress", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")

		assert.Equal(t, "POST", req.Method)
		assert.Contains(t, string(body), "koreader")
		assert.Contains(t, string(body), "kobo")
	})

	t.Run("no conflict when progress difference is less than 1%", func(t *testing.T) {
		progressData := map[string]interface{}{
			"percentage": 0.45,
		}

		existingProgress := map[string]interface{}{
			"percentage": 0.451,
		}

		diff := progressData["percentage"].(float64) - existingProgress["percentage"].(float64)
		if diff < 0 {
			diff = -diff
		}

		assert.Less(t, diff, 0.01, "Should not trigger conflict for small differences")
	})

	t.Run("no conflict when sync timestamps are more than 5 minutes apart", func(t *testing.T) {
		timestamp1 := "2026-01-30T20:00:00Z"
		timestamp2 := "2026-01-30T20:10:00Z"

		var conflictDetected bool
		if timestamp2 > timestamp1 {
			conflictDetected = false
		}

		assert.False(t, conflictDetected, "Should not trigger conflict for old syncs")
	})
}

func TestConflictResolution_ChoosingWinner(t *testing.T) {
	t.Run("resolve conflict by choosing koreader source", func(t *testing.T) {
		conflictID := uuid.New()

		reqBody := map[string]interface{}{
			"winner":                        "koreader",
			"manual_data":                   nil,
			"apply_to_all_future_conflicts": false,
			"reason":                        "More recent progress",
		}

		body, err := json.Marshal(reqBody)
		assert.NoError(t, err)

		req := httptest.NewRequest("POST", "/api/conflicts/"+conflictID.String()+"/resolve", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")

		assert.Equal(t, "POST", req.Method)
		assert.Contains(t, req.URL.Path, conflictID.String())
		assert.Contains(t, string(body), "koreader")
	})

	t.Run("resolve conflict with manual merge data", func(t *testing.T) {
		conflictID := uuid.New()

		manualData := map[string]interface{}{
			"percentage": 0.43,
			"epubcfi":    "epubcfi(/6/4/2:20)",
			"chapter":    3,
			"page":       90,
		}

		reqBody := map[string]interface{}{
			"winner":                        "manual",
			"manual_data":                   manualData,
			"apply_to_all_future_conflicts": false,
			"reason":                        "Custom merged position",
		}

		body, err := json.Marshal(reqBody)
		assert.NoError(t, err)

		req := httptest.NewRequest("POST", "/api/conflicts/"+conflictID.String()+"/resolve", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")

		assert.Contains(t, string(body), "manual")
		assert.Contains(t, string(body), "0.43")
	})

	t.Run("error when winner is manual but no manual_data provided", func(t *testing.T) {
		conflictID := uuid.New()

		reqBody := map[string]interface{}{
			"winner":                        "manual",
			"manual_data":                   nil,
			"apply_to_all_future_conflicts": false,
			"reason":                        "Test",
		}

		body, err := json.Marshal(reqBody)
		assert.NoError(t, err)

		req := httptest.NewRequest("POST", "/api/conflicts/"+conflictID.String()+"/resolve", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")

		assert.Contains(t, string(body), "manual")
	})
}

func TestConflictListing_Filtering(t *testing.T) {
	t.Run("list only unresolved conflicts", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/api/conflicts?status=unresolved", nil)
		assert.Equal(t, "GET", req.Method)
		assert.Contains(t, req.URL.Query().Get("status"), "unresolved")
	})

	t.Run("list all conflicts regardless of status", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/api/conflicts?status=all", nil)
		assert.Equal(t, "GET", req.Method)
		assert.Contains(t, req.URL.Query().Get("status"), "all")
	})

	t.Run("list only resolved conflicts", func(t *testing.T) {
		req := httptest.NewRequest("GET", "/api/conflicts?status=user_resolved", nil)
		assert.Equal(t, "GET", req.Method)
		assert.Contains(t, req.URL.Query().Get("status"), "user_resolved")
	})
}

func TestConflictResponse_Structure(t *testing.T) {
	t.Run("conflict detail response includes all required fields", func(t *testing.T) {
		conflictResponse := map[string]interface{}{
			"id":                "conflict-uuid-123",
			"media_item_id":     "book-uuid-456",
			"media_item_title":  "Test Book Title",
			"conflict_type":     "progress",
			"resolution_status": "unresolved",
			"created_at":        "2026-01-30T20:10:00Z",
			"conflict_data": map[string]interface{}{
				"koreader": map[string]interface{}{
					"source": "koreader",
					"data": map[string]interface{}{
						"percentage": 0.45,
					},
				},
				"kobo": map[string]interface{}{
					"source": "kobo",
					"data": map[string]interface{}{
						"percentage": 0.42,
					},
				},
			},
		}

		body, err := json.Marshal(conflictResponse)
		assert.NoError(t, err)

		var parsed map[string]interface{}
		err = json.Unmarshal(body, &parsed)
		assert.NoError(t, err)

		assert.Contains(t, parsed, "id")
		assert.Contains(t, parsed, "media_item_id")
		assert.Contains(t, parsed, "conflict_data")
		assert.Contains(t, parsed["conflict_data"].(map[string]interface{}), "koreader")
		assert.Contains(t, parsed["conflict_data"].(map[string]interface{}), "kobo")
	})

	t.Run("conflict list response includes summary counts", func(t *testing.T) {
		listResponse := map[string]interface{}{
			"conflicts": []interface{}{
				map[string]string{"id": "conflict-1", "resolution_status": "unresolved"},
				map[string]string{"id": "conflict-2", "resolution_status": "unresolved"},
			},
			"total":      2,
			"unresolved": 2,
		}

		body, err := json.Marshal(listResponse)
		assert.NoError(t, err)

		var parsed map[string]interface{}
		err = json.Unmarshal(body, &parsed)
		assert.NoError(t, err)

		assert.Equal(t, float64(2), parsed["total"])
		assert.Equal(t, float64(2), parsed["unresolved"])
	})
}

func TestConflictDeletion(t *testing.T) {
	t.Run("delete single conflict by ID", func(t *testing.T) {
		conflictID := uuid.New()

		req := httptest.NewRequest("DELETE", "/api/conflicts/"+conflictID.String(), nil)
		assert.Equal(t, "DELETE", req.Method)
		assert.Contains(t, req.URL.Path, conflictID.String())
	})

	t.Run("dismiss all resolved conflicts", func(t *testing.T) {
		req := httptest.NewRequest("POST", "/api/conflicts/dismiss-all", nil)
		assert.Equal(t, "POST", req.Method)
		assert.Contains(t, req.URL.Path, "dismiss-all")
	})
}

func TestConflictNotification_WebSocketBroadcast(t *testing.T) {
	t.Run("conflict detection notification", func(t *testing.T) {
		notification := map[string]interface{}{
			"type":      "conflict",
			"timestamp": "2026-01-30T20:10:00Z",
			"data": map[string]interface{}{
				"book_id":           "book-uuid-123",
				"notification_type": "detection",
				"conflict_id":       "",
			},
		}

		body, err := json.Marshal(notification)
		assert.NoError(t, err)

		var parsed map[string]interface{}
		err = json.Unmarshal(body, &parsed)
		assert.NoError(t, err)

		data := parsed["data"].(map[string]interface{})
		assert.Equal(t, "detection", data["notification_type"])
	})

	t.Run("conflict resolved notification", func(t *testing.T) {
		conflictID := uuid.New()

		notification := map[string]interface{}{
			"type":      "conflict",
			"timestamp": "2026-01-30T20:15:00Z",
			"data": map[string]interface{}{
				"book_id":           "book-uuid-123",
				"notification_type": "resolved",
				"conflict_id":       conflictID.String(),
			},
		}

		body, err := json.Marshal(notification)
		assert.NoError(t, err)

		var parsed map[string]interface{}
		err = json.Unmarshal(body, &parsed)
		assert.NoError(t, err)

		data := parsed["data"].(map[string]interface{})
		assert.Equal(t, "resolved", data["notification_type"])
		assert.Equal(t, conflictID.String(), data["conflict_id"])
	})
}
