package sync

import (
	"testing"

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

func TestPageToPercentage(t *testing.T) {
	tests := []struct {
		name       string
		page       int
		totalPages int
		want       float64
	}{
		{
			name:       "Beginning of book",
			page:       0,
			totalPages: 200,
			want:       0.0,
		},
		{
			name:       "Middle of book",
			page:       100,
			totalPages: 200,
			want:       0.5,
		},
		{
			name:       "End of book",
			page:       200,
			totalPages: 200,
			want:       1.0,
		},
		{
			name:       "25% through book",
			page:       50,
			totalPages: 200,
			want:       0.25,
		},
		{
			name:       "Clamp to 1.0",
			page:       250,
			totalPages: 200,
			want:       1.0,
		},
		{
			name:       "Clamp to 0.0",
			page:       -10,
			totalPages: 200,
			want:       0.0,
		},
		{
			name:       "Zero total pages",
			page:       100,
			totalPages: 0,
			want:       0.0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := PageToPercentage(tt.page, tt.totalPages)
			assert.InDelta(t, tt.want, got, 0.001)
		})
	}
}

func TestPercentageToPage(t *testing.T) {
	tests := []struct {
		name       string
		percentage float64
		totalPages int
		want       int
	}{
		{
			name:       "0% progress",
			percentage: 0.0,
			totalPages: 200,
			want:       0,
		},
		{
			name:       "50% progress",
			percentage: 0.5,
			totalPages: 200,
			want:       100,
		},
		{
			name:       "100% progress",
			percentage: 1.0,
			totalPages: 200,
			want:       200,
		},
		{
			name:       "25% progress",
			percentage: 0.25,
			totalPages: 200,
			want:       50,
		},
		{
			name:       "Clamp below 0",
			percentage: -0.1,
			totalPages: 200,
			want:       0,
		},
		{
			name:       "Clamp above 1",
			percentage: 1.5,
			totalPages: 200,
			want:       200,
		},
		{
			name:       "33.3% progress",
			percentage: 0.333,
			totalPages: 300,
			want:       100,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := PercentageToPage(tt.percentage, tt.totalPages)
			assert.Equal(t, tt.want, got)
		})
	}
}

func TestCharacterToPercentage(t *testing.T) {
	tests := []struct {
		name            string
		character       int64
		totalCharacters int64
		want            float64
	}{
		{
			name:            "Beginning of book",
			character:       0,
			totalCharacters: 100000,
			want:            0.0,
		},
		{
			name:            "Middle of book",
			character:       50000,
			totalCharacters: 100000,
			want:            0.5,
		},
		{
			name:            "End of book",
			character:       100000,
			totalCharacters: 100000,
			want:            1.0,
		},
		{
			name:            "25% through book",
			character:       25000,
			totalCharacters: 100000,
			want:            0.25,
		},
		{
			name:            "Clamp to 1.0",
			character:       150000,
			totalCharacters: 100000,
			want:            1.0,
		},
		{
			name:            "Zero total characters",
			character:       50000,
			totalCharacters: 0,
			want:            0.0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := CharacterToPercentage(tt.character, tt.totalCharacters)
			assert.InDelta(t, tt.want, got, 0.001)
		})
	}
}

func TestPercentageToCharacter(t *testing.T) {
	tests := []struct {
		name            string
		percentage      float64
		totalCharacters int64
		want            int64
	}{
		{
			name:            "0% progress",
			percentage:      0.0,
			totalCharacters: 100000,
			want:            0,
		},
		{
			name:            "50% progress",
			percentage:      0.5,
			totalCharacters: 100000,
			want:            50000,
		},
		{
			name:            "100% progress",
			percentage:      1.0,
			totalCharacters: 100000,
			want:            100000,
		},
		{
			name:            "25% progress",
			percentage:      0.25,
			totalCharacters: 100000,
			want:            25000,
		},
		{
			name:            "Clamp below 0",
			percentage:      -0.1,
			totalCharacters: 100000,
			want:            0,
		},
		{
			name:            "Clamp above 1",
			percentage:      1.5,
			totalCharacters: 100000,
			want:            100000,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := PercentageToCharacter(tt.percentage, tt.totalCharacters)
			assert.Equal(t, tt.want, got)
		})
	}
}

func TestConvertProgress_ReflowableToFixed(t *testing.T) {
	source := map[string]interface{}{
		"percentage":       0.45,
		"epubcfi":          "epubcfi(/6/4/2:45)",
		"total_characters": int64(100000),
	}

	result, err := ConvertProgress(FormatGroupReflowable, FormatGroupFixedLayout, source)
	assert.NoError(t, err)

	assert.InDelta(t, 0.45, result["percentage"], 0.001)
	assert.Equal(t, 90, result["page"])
	assert.Equal(t, 200, result["total_pages"])
}

func TestConvertProgress_FixedToReflowable(t *testing.T) {
	source := map[string]interface{}{
		"page":        100,
		"total_pages": 200,
	}

	result, err := ConvertProgress(FormatGroupFixedLayout, FormatGroupReflowable, source)
	assert.NoError(t, err)

	assert.InDelta(t, 0.5, result["percentage"], 0.001)
	assert.Contains(t, result["epubcfi"], "epubcfi")
}

func TestConvertProgress_ComicToReflowable(t *testing.T) {
	source := map[string]interface{}{
		"page":        16,
		"total_pages": 32,
	}

	result, err := ConvertProgress(FormatGroupComicArchive, FormatGroupReflowable, source)
	assert.NoError(t, err)

	assert.InDelta(t, 0.5, result["percentage"], 0.001)
	assert.Contains(t, result["epubcfi"], "epubcfi")
}

func TestConvertProgress_UnsupportedFormat(t *testing.T) {
	source := map[string]interface{}{
		"page":        100,
		"total_pages": 200,
	}

	result, err := ConvertProgress(FormatGroupUnknown, FormatGroupReflowable, source)
	assert.NoError(t, err)
	assert.Equal(t, 0.0, result["percentage"])
}

func TestMergeProgress(t *testing.T) {
	tests := []struct {
		name        string
		progressA   map[string]interface{}
		progressB   map[string]interface{}
		wantPercent float64
		wantSource  string
	}{
		{
			name: "Progress A wins",
			progressA: map[string]interface{}{
				"source":     "koreader",
				"percentage": 0.60,
			},
			progressB: map[string]interface{}{
				"source":     "kobo",
				"percentage": 0.40,
			},
			wantPercent: 0.60,
			wantSource:  "koreader",
		},
		{
			name: "Progress B wins",
			progressA: map[string]interface{}{
				"source":     "koreader",
				"percentage": 0.30,
			},
			progressB: map[string]interface{}{
				"source":     "kobo",
				"percentage": 0.70,
			},
			wantPercent: 0.70,
			wantSource:  "kobo",
		},
		{
			name: "Equal progress keeps B",
			progressA: map[string]interface{}{
				"source":     "koreader",
				"percentage": 0.50,
			},
			progressB: map[string]interface{}{
				"source":     "kobo",
				"percentage": 0.50,
			},
			wantPercent: 0.50,
			wantSource:  "kobo",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := MergeProgress(tt.progressA, tt.progressB)

			percent, ok := result["percentage"].(float64)
			assert.True(t, ok)
			assert.InDelta(t, tt.wantPercent, percent, 0.001)

			sources, ok := result["merged_from"].([]string)
			assert.True(t, ok)
			assert.Len(t, sources, 2)
			assert.Contains(t, result["merge_timestamp"], "now")
		})
	}
}

func TestFormatProgressForDisplay(t *testing.T) {
	tests := []struct {
		name        string
		formatGroup FormatGroup
		progress    map[string]interface{}
		want        string
	}{
		{
			name:        "Reflowable with chapter",
			formatGroup: FormatGroupReflowable,
			progress: map[string]interface{}{
				"percentage": 0.45678,
				"chapter":    3,
			},
			want: "45.7% (Chapter 3)",
		},
		{
			name:        "Reflowable without chapter",
			formatGroup: FormatGroupReflowable,
			progress: map[string]interface{}{
				"percentage": 0.45678,
			},
			want: "45.7%",
		},
		{
			name:        "Fixed layout with pages",
			formatGroup: FormatGroupFixedLayout,
			progress: map[string]interface{}{
				"percentage":  0.45,
				"page":        90,
				"total_pages": 200,
			},
			want: "Page 90 of 200 (45.0%)",
		},
		{
			name:        "Fixed layout percentage only",
			formatGroup: FormatGroupFixedLayout,
			progress: map[string]interface{}{
				"percentage": 0.45,
			},
			want: "45.0%",
		},
		{
			name:        "Comic archive with pages",
			formatGroup: FormatGroupComicArchive,
			progress: map[string]interface{}{
				"percentage":  0.375,
				"page":        12,
				"total_pages": 32,
			},
			want: "Page 12 of 32 (38%)",
		},
		{
			name:        "Unknown format defaults to 0%",
			formatGroup: FormatGroupUnknown,
			progress: map[string]interface{}{
				"page":        50,
				"total_pages": 100,
			},
			want: "0.0%",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := FormatProgressForDisplay(tt.formatGroup, tt.progress)
			assert.Equal(t, tt.want, got)
		})
	}
}

func TestRoundTripConversion(t *testing.T) {
	t.Run("Page -> Percentage -> Page", func(t *testing.T) {
		originalPage := 75
		totalPages := 300

		percentage := PageToPercentage(originalPage, totalPages)
		resultPage := PercentageToPage(percentage, totalPages)

		assert.Equal(t, originalPage, resultPage)
	})

	t.Run("Character -> Percentage -> Character", func(t *testing.T) {
		originalChar := int64(25000)
		totalChars := int64(100000)

		percentage := CharacterToPercentage(originalChar, totalChars)
		resultChar := PercentageToCharacter(percentage, totalChars)

		assert.Equal(t, originalChar, resultChar)
	})
}

func TestEdgeCases(t *testing.T) {
	t.Run("Very small percentages", func(t *testing.T) {
		assert.InDelta(t, 0.001, PageToPercentage(1, 1000), 0.0001)
	})

	t.Run("Very large page numbers", func(t *testing.T) {
		page := PercentageToPage(0.999, 10000)
		assert.Equal(t, 9990, page)
	})

	t.Run("Floating point precision", func(t *testing.T) {
		percentage := 1.0 / 3.0
		page := PercentageToPage(percentage, 300)
		assert.InDelta(t, 100, page, 1)
	})
}

func TestBuildProgressSnapshot(t *testing.T) {
	t.Run("nil fields are omitted", func(t *testing.T) {
		pct := 0.5
		ch := 3
		req := SaveProgressRequest{
			Percentage: &pct,
			Chapter:    &ch,
		}
		snapshot := buildProgressSnapshot(req)
		assert.InDelta(t, 0.5, snapshot["percentage"], 0.001)
		assert.Equal(t, 3, snapshot["chapter"])
		_, hasEpubcfi := snapshot["epubcfi"]
		assert.False(t, hasEpubcfi)
	})

	t.Run("all fields present", func(t *testing.T) {
		pct := 0.75
		cfi := "epubcfi(/6/4/2:10)"
		ch := 5
		charOff := int64(10000)
		page := 150
		tp := 200
		req := SaveProgressRequest{
			Percentage:      &pct,
			Epubcfi:         &cfi,
			Chapter:         &ch,
			CharacterOffset: &charOff,
			CurrentPage:     &page,
			TotalPages:      &tp,
		}
		snapshot := buildProgressSnapshot(req)
		assert.InDelta(t, 0.75, snapshot["percentage"], 0.001)
		assert.Equal(t, "epubcfi(/6/4/2:10)", snapshot["epubcfi"])
		assert.Equal(t, 5, snapshot["chapter"])
		assert.Equal(t, int64(10000), snapshot["character"])
		assert.Equal(t, 150, snapshot["page"])
		assert.Equal(t, 200, snapshot["total_pages"])
	})
}
