package com.example.notesai

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStoreFile
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.example.notesai.data.local.DataStoreManager
import com.example.notesai.data.model.Category
import com.example.notesai.data.model.Note
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.util.UUID

/**
 * Unit Test untuk DataStoreManager
 * Coverage:
 * - TC-01: Create note & category + autosave debounce 500ms
 * - TC-02: Pin note muncul di urutan teratas
 */
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class DataStoreManagerTest {

    private lateinit var context: Context
    private lateinit var dataStore: DataStore<Preferences>
    private lateinit var dataStoreManager: DataStoreManager
    private val testDispatcher = StandardTestDispatcher()
    private val testScope = TestScope(testDispatcher + Job())

    @Before
    fun setup() {
        Dispatchers.setMain(testDispatcher)
        context = ApplicationProvider.getApplicationContext()

        // Create test DataStore with unique name
        val testDataStoreName = "test_notes_prefs_${UUID.randomUUID()}"
        dataStore = PreferenceDataStoreFactory.create(
            scope = testScope,
            produceFile = { context.preferencesDataStoreFile(testDataStoreName) }
        )

        dataStoreManager = DataStoreManager(context)
    }

    @After
    fun tearDown() {
        testScope.cancel()
        Dispatchers.resetMain()
    }

    // ================== TC-01: CREATE NOTE & CATEGORY ==================

    @Test
    fun testCreateCategory_shouldSaveSuccessfully() = testScope.runTest {
        // Given
        val category = Category(
            id = "cat_001",
            name = "Work",
            gradientStart = 0xFFE91E63,
            gradientEnd = 0xFF9C27B0,
            timestamp = System.currentTimeMillis(),
            isDeleted = false,
            isPinned = false
        )

        // When
        dataStoreManager.saveCategories(listOf(category))
        advanceUntilIdle()

        // Then
        val savedCategories = dataStoreManager.categoriesFlow.first()
        Assert.assertEquals(1, savedCategories.size)
        Assert.assertEquals("Work", savedCategories[0].name)
        Assert.assertEquals("cat_001", savedCategories[0].id)
    }

    @Test
    fun testCreateNote_shouldSaveSuccessfully() = testScope.runTest {
        // Given
        val note = Note(
            id = "note_001",
            categoryId = "cat_001",
            title = "Meeting Notes",
            description = "Team meeting summary",
            content = "Discussed project timeline",
            timestamp = System.currentTimeMillis(),
            isPinned = false,
            isArchived = false,
            isDeleted = false
        )

        // When
        dataStoreManager.saveNotes(listOf(note))
        advanceUntilIdle()

        // Then
        val savedNotes = dataStoreManager.notesFlow.first()
        Assert.assertEquals(1, savedNotes.size)
        Assert.assertEquals("Meeting Notes", savedNotes[0].title)
        Assert.assertEquals("note_001", savedNotes[0].id)
    }

    @Test
    fun testCreateMultipleCategories_shouldSaveInCorrectOrder() = testScope.runTest {
        // Given
        val categories = listOf(
            Category(
                id = "cat_001",
                name = "Work",
                gradientStart = 0xFF000000,
                gradientEnd = 0xFF111111
            ),
            Category(
                id = "cat_002",
                name = "Personal",
                gradientStart = 0xFF222222,
                gradientEnd = 0xFF333333
            ),
            Category(
                id = "cat_003",
                name = "Ideas",
                gradientStart = 0xFF444444,
                gradientEnd = 0xFF555555
            )
        )

        // When
        dataStoreManager.saveCategories(categories)
        advanceUntilIdle()

        // Then
        val savedCategories = dataStoreManager.categoriesFlow.first()
        Assert.assertEquals(3, savedCategories.size)
        Assert.assertEquals("Work", savedCategories[0].name)
        Assert.assertEquals("Personal", savedCategories[1].name)
        Assert.assertEquals("Ideas", savedCategories[2].name)
    }

    @Test
    fun testAutoSave_shouldUpdateExistingNote() = testScope.runTest {
        // Given - Initial note
        val initialNote = Note(
            id = "note_001",
            categoryId = "cat_001",
            title = "Draft",
            content = "Initial content",
            timestamp = System.currentTimeMillis()
        )

        dataStoreManager.saveNotes(listOf(initialNote))
        advanceUntilIdle()

        // When - Simulate autosave with updated content (debounce simulation)
        val updatedNote = initialNote.copy(
            title = "Updated Draft",
            content = "Updated content after typing"
        )

        // Simulate 500ms debounce
        delay(500)
        dataStoreManager.saveNotes(listOf(updatedNote))
        advanceUntilIdle()

        // Then
        val savedNotes = dataStoreManager.notesFlow.first()
        Assert.assertEquals(1, savedNotes.size)
        Assert.assertEquals("Updated Draft", savedNotes[0].title)
        Assert.assertEquals("Updated content after typing", savedNotes[0].content)
    }

    // ================== TC-02: PIN NOTE - URUTAN TERATAS ==================

    @Test
    fun testPinNote_shouldAppearFirst() = testScope.runTest {
        // Given - Create 3 notes with different timestamps
        val now = System.currentTimeMillis()
        val notes = listOf(
            Note(
                id = "note_001",
                categoryId = "cat_001",
                title = "Oldest",
                timestamp = now - 2000,
                isPinned = false
            ),
            Note(
                id = "note_002",
                categoryId = "cat_001",
                title = "Middle",
                timestamp = now - 1000,
                isPinned = false
            ),
            Note(
                id = "note_003",
                categoryId = "cat_001",
                title = "Newest",
                timestamp = now,
                isPinned = false
            )
        )

        dataStoreManager.saveNotes(notes)
        advanceUntilIdle()

        // When - Pin the oldest note
        val pinnedNotes = notes.map {
            if (it.id == "note_001") it.copy(isPinned = true) else it
        }
        dataStoreManager.saveNotes(pinnedNotes)
        advanceUntilIdle()

        // Then - Sort by pinned then timestamp (like in MainScreen.kt)
        val savedNotes = dataStoreManager.notesFlow.first()
        val sortedNotes = savedNotes
            .sortedWith(compareByDescending<Note> { it.isPinned }.thenByDescending { it.timestamp })

        Assert.assertEquals("Oldest", sortedNotes[0].title)
        Assert.assertTrue(sortedNotes[0].isPinned)
    }

    @Test
    fun testMultiplePinnedNotes_shouldSortByTimestamp() = testScope.runTest {
        // Given - Create notes with 2 pinned
        val now = System.currentTimeMillis()
        val notes = listOf(
            Note(
                id = "note_001",
                categoryId = "cat_001",
                title = "Pinned Old",
                timestamp = now - 3000,
                isPinned = true
            ),
            Note(
                id = "note_002",
                categoryId = "cat_001",
                title = "Normal",
                timestamp = now - 2000,
                isPinned = false
            ),
            Note(
                id = "note_003",
                categoryId = "cat_001",
                title = "Pinned New",
                timestamp = now - 1000,
                isPinned = true
            ),
            Note(
                id = "note_004",
                categoryId = "cat_001",
                title = "Newest Normal",
                timestamp = now,
                isPinned = false
            )
        )

        dataStoreManager.saveNotes(notes)
        advanceUntilIdle()

        // When - Get sorted notes
        val savedNotes = dataStoreManager.notesFlow.first()
        val sortedNotes = savedNotes
            .sortedWith(compareByDescending<Note> { it.isPinned }.thenByDescending { it.timestamp })

        // Then - Pinned notes should be first, sorted by timestamp
        Assert.assertEquals(4, sortedNotes.size)
        Assert.assertEquals("Pinned New", sortedNotes[0].title) // Pinned, newest
        Assert.assertEquals("Pinned Old", sortedNotes[1].title) // Pinned, older
        Assert.assertEquals("Newest Normal", sortedNotes[2].title) // Not pinned, newest
        Assert.assertEquals("Normal", sortedNotes[3].title) // Not pinned, older

        Assert.assertTrue(sortedNotes[0].isPinned)
        Assert.assertTrue(sortedNotes[1].isPinned)
        Assert.assertFalse(sortedNotes[2].isPinned)
        Assert.assertFalse(sortedNotes[3].isPinned)
    }

    @Test
    fun testUnpinNote_shouldMoveToNormalPosition() = testScope.runTest {
        // Given - Note that is pinned
        val now = System.currentTimeMillis()
        val notes = listOf(
            Note(
                id = "note_001",
                categoryId = "cat_001",
                title = "Old",
                timestamp = now - 2000,
                isPinned = false
            ),
            Note(
                id = "note_002",
                categoryId = "cat_001",
                title = "Pinned",
                timestamp = now - 1000,
                isPinned = true
            ),
            Note(
                id = "note_003",
                categoryId = "cat_001",
                title = "New",
                timestamp = now,
                isPinned = false
            )
        )

        dataStoreManager.saveNotes(notes)
        advanceUntilIdle()

        // When - Unpin the note
        val unpinnedNotes = notes.map {
            if (it.id == "note_002") it.copy(isPinned = false) else it
        }
        dataStoreManager.saveNotes(unpinnedNotes)
        advanceUntilIdle()

        // Then - Should be sorted by timestamp only
        val savedNotes = dataStoreManager.notesFlow.first()
        val sortedNotes = savedNotes
            .sortedWith(compareByDescending<Note> { it.isPinned }.thenByDescending { it.timestamp })

        Assert.assertEquals("New", sortedNotes[0].title)
        Assert.assertEquals("Pinned", sortedNotes[1].title) // Now in middle based on timestamp
        Assert.assertEquals("Old", sortedNotes[2].title)

        Assert.assertFalse(sortedNotes[0].isPinned)
        Assert.assertFalse(sortedNotes[1].isPinned)
        Assert.assertFalse(sortedNotes[2].isPinned)
    }

    @Test
    fun testPinCategory_shouldPersist() = testScope.runTest {
        // Given
        val category = Category(
            id = "cat_001",
            name = "Important",
            gradientStart = 0xFFE91E63,
            gradientEnd = 0xFF9C27B0,
            isPinned = false
        )

        dataStoreManager.saveCategories(listOf(category))
        advanceUntilIdle()

        // When - Pin category
        val pinnedCategory = category.copy(isPinned = true)
        dataStoreManager.saveCategories(listOf(pinnedCategory))
        advanceUntilIdle()

        // Then
        val savedCategories = dataStoreManager.categoriesFlow.first()
        Assert.assertEquals(1, savedCategories.size)
        Assert.assertTrue(savedCategories[0].isPinned)
    }
}