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.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
import java.util.UUID

/**
 * Unit Test untuk Search Functionality
 * Coverage:
 * - TC-04: Search realtime menemukan keyword
 */
@RunWith(AndroidJUnit4::class)
class SearchFunctionalityTest {

    private lateinit var context: Context
    private lateinit var dataStore: DataStore<Preferences>
    private lateinit var dataStoreManager: DataStoreManager
    private lateinit var testScope: CoroutineScope

    @Before
    fun setup() {
        context = ApplicationProvider.getApplicationContext()
        testScope = CoroutineScope(SupervisorJob())

        val testDataStoreName = "test_search_prefs_${UUID.randomUUID()}"
        dataStore = PreferenceDataStoreFactory.create(
            scope = testScope,
            produceFile = { context.preferencesDataStoreFile(testDataStoreName) }
        )

        dataStoreManager = DataStoreManager(context)
    }

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

    // ================== TC-04: SEARCH REALTIME ==================

    @Test
    fun testSearchNoteByTitle_shouldFindMatches() = runBlocking {
        // Given - Create multiple notes
        val notes = listOf(
            Note(id = "note_001", categoryId = "cat_001", title = "Meeting Notes", content = "Discuss project", isDeleted = false),
            Note(id = "note_002", categoryId = "cat_001", title = "Shopping List", content = "Buy groceries", isDeleted = false),
            Note(id = "note_003", categoryId = "cat_001", title = "Project Ideas", content = "Brainstorm features", isDeleted = false)
        )

        dataStoreManager.saveNotes(notes)

        // When - Search for "Meeting" (simulating MainScreen search behavior)
        val savedNotes = dataStoreManager.notesFlow.first()
        val searchQuery = "Meeting"
        val searchResults = savedNotes.filter { note ->
            !note.isDeleted && !note.isArchived &&
                    (note.title.contains(searchQuery, ignoreCase = true) ||
                            note.content.contains(searchQuery, ignoreCase = true))
        }

        // Then - Should find 1 note
        assertEquals(1, searchResults.size)
        assertEquals("Meeting Notes", searchResults[0].title)
    }

    @Test
    fun testSearchNoteByContent_shouldFindMatches() = runBlocking {
        // Given - Notes with specific content
        val notes = listOf(
            Note(id = "note_001", categoryId = "cat_001", title = "Random Title", content = "This contains Kotlin code", isDeleted = false),
            Note(id = "note_002", categoryId = "cat_001", title = "Another Note", content = "JavaScript examples here", isDeleted = false),
            Note(id = "note_003", categoryId = "cat_001", title = "Third Note", content = "Python tutorials", isDeleted = false)
        )

        dataStoreManager.saveNotes(notes)

        // When - Search for "Kotlin"
        val savedNotes = dataStoreManager.notesFlow.first()
        val searchQuery = "Kotlin"
        val searchResults = savedNotes.filter { note ->
            !note.isDeleted && !note.isArchived &&
                    (note.title.contains(searchQuery, ignoreCase = true) ||
                            note.content.contains(searchQuery, ignoreCase = true))
        }

        // Then - Should find note with Kotlin in content
        assertEquals(1, searchResults.size)
        assertEquals("Random Title", searchResults[0].title)
        assertTrue(searchResults[0].content.contains("Kotlin"))
    }

    @Test
    fun testSearchCaseInsensitive_shouldFindMatches() = runBlocking {
        // Given - Note with mixed case title
        val note = Note(
            id = "note_001",
            categoryId = "cat_001",
            title = "Android Development",
            content = "Building mobile apps",
            isDeleted = false
        )

        dataStoreManager.saveNotes(listOf(note))

        // When - Search with different cases
        val savedNotes = dataStoreManager.notesFlow.first()

        val searchQueries = listOf("android", "ANDROID", "Android", "AnDrOiD")

        searchQueries.forEach { searchQuery ->
            val searchResults = savedNotes.filter { n ->
                !n.isDeleted && !n.isArchived &&
                        (n.title.contains(searchQuery, ignoreCase = true) ||
                                n.content.contains(searchQuery, ignoreCase = true))
            }

            // Then - Should find match regardless of case
            assertEquals("Failed for query: $searchQuery", 1, searchResults.size)
        }
    }

    @Test
    fun testSearchPartialMatch_shouldFindResults() = runBlocking {
        // Given - Notes with various titles
        val notes = listOf(
            Note(id = "note_001", categoryId = "cat_001", title = "Development Guide", isDeleted = false),
            Note(id = "note_002", categoryId = "cat_001", title = "Developer Tools", isDeleted = false),
            Note(id = "note_003", categoryId = "cat_001", title = "Design Patterns", isDeleted = false)
        )

        dataStoreManager.saveNotes(notes)

        // When - Search for partial word "Dev"
        val savedNotes = dataStoreManager.notesFlow.first()
        val searchQuery = "Dev"
        val searchResults = savedNotes.filter { note ->
            !note.isDeleted && !note.isArchived &&
                    (note.title.contains(searchQuery, ignoreCase = true) ||
                            note.content.contains(searchQuery, ignoreCase = true))
        }

        // Then - Should find 2 notes containing "Dev"
        assertEquals(2, searchResults.size)
        assertTrue(searchResults.any { it.title == "Development Guide" })
        assertTrue(searchResults.any { it.title == "Developer Tools" })
    }

    @Test
    fun testSearchEmptyQuery_shouldReturnAllNotes() = runBlocking {
        // Given - Multiple notes
        val notes = listOf(
            Note(id = "note_001", categoryId = "cat_001", title = "Note 1", isDeleted = false),
            Note(id = "note_002", categoryId = "cat_001", title = "Note 2", isDeleted = false),
            Note(id = "note_003", categoryId = "cat_001", title = "Note 3", isDeleted = false)
        )

        dataStoreManager.saveNotes(notes)

        // When - Search with empty query
        val savedNotes = dataStoreManager.notesFlow.first()
        val searchQuery = ""
        val searchResults = if (searchQuery.isEmpty()) {
            savedNotes.filter { !it.isDeleted && !it.isArchived }
        } else {
            savedNotes.filter { note ->
                !note.isDeleted && !note.isArchived &&
                        (note.title.contains(searchQuery, ignoreCase = true) ||
                                note.content.contains(searchQuery, ignoreCase = true))
            }
        }

        // Then - Should return all active notes
        assertEquals(3, searchResults.size)
    }

    @Test
    fun testSearchNoMatches_shouldReturnEmpty() = runBlocking {
        // Given - Notes that don't match search
        val notes = listOf(
            Note(id = "note_001", categoryId = "cat_001", title = "Note 1", content = "Content 1", isDeleted = false),
            Note(id = "note_002", categoryId = "cat_001", title = "Note 2", content = "Content 2", isDeleted = false)
        )

        dataStoreManager.saveNotes(notes)

        // When - Search for non-existent keyword
        val savedNotes = dataStoreManager.notesFlow.first()
        val searchQuery = "NonExistentKeyword"
        val searchResults = savedNotes.filter { note ->
            !note.isDeleted && !note.isArchived &&
                    (note.title.contains(searchQuery, ignoreCase = true) ||
                            note.content.contains(searchQuery, ignoreCase = true))
        }

        // Then - Should return empty
        assertEquals(0, searchResults.size)
    }

    @Test
    fun testSearchExcludesDeletedNotes_shouldNotFindDeleted() = runBlocking {
        // Given - Mix of active and deleted notes with same keyword
        val notes = listOf(
            Note(id = "note_001", categoryId = "cat_001", title = "Active Kotlin Note", isDeleted = false),
            Note(id = "note_002", categoryId = "cat_001", title = "Deleted Kotlin Note", isDeleted = true),
            Note(id = "note_003", categoryId = "cat_001", title = "Another Kotlin Note", isDeleted = false)
        )

        dataStoreManager.saveNotes(notes)

        // When - Search for "Kotlin"
        val savedNotes = dataStoreManager.notesFlow.first()
        val searchQuery = "Kotlin"
        val searchResults = savedNotes.filter { note ->
            !note.isDeleted && !note.isArchived &&
                    (note.title.contains(searchQuery, ignoreCase = true) ||
                            note.content.contains(searchQuery, ignoreCase = true))
        }

        // Then - Should only find 2 active notes
        assertEquals(2, searchResults.size)
        assertTrue(searchResults.all { !it.isDeleted })
        assertFalse(searchResults.any { it.title == "Deleted Kotlin Note" })
    }

    @Test
    fun testSearchExcludesArchivedNotes_shouldNotFindArchived() = runBlocking {
        // Given - Mix of active and archived notes
        val notes = listOf(
            Note(id = "note_001", categoryId = "cat_001", title = "Active Project", isArchived = false),
            Note(id = "note_002", categoryId = "cat_001", title = "Archived Project", isArchived = true),
            Note(id = "note_003", categoryId = "cat_001", title = "Another Project", isArchived = false)
        )

        dataStoreManager.saveNotes(notes)

        // When - Search for "Project"
        val savedNotes = dataStoreManager.notesFlow.first()
        val searchQuery = "Project"
        val searchResults = savedNotes.filter { note ->
            !note.isDeleted && !note.isArchived &&
                    (note.title.contains(searchQuery, ignoreCase = true) ||
                            note.content.contains(searchQuery, ignoreCase = true))
        }

        // Then - Should only find 2 active notes
        assertEquals(2, searchResults.size)
        assertTrue(searchResults.all { !it.isArchived })
        assertFalse(searchResults.any { it.title == "Archived Project" })
    }

    @Test
    fun testSearchCategory_shouldFindByName() = runBlocking {
        // Given - Multiple categories
        val categories = listOf(
            Category(id = "cat_001", name = "Work Projects", gradientStart = 0xFF000000, gradientEnd = 0xFF111111),
            Category(id = "cat_002", name = "Personal Tasks", gradientStart = 0xFF222222, gradientEnd = 0xFF333333),
            Category(id = "cat_003", name = "Ideas and Notes", gradientStart = 0xFF444444, gradientEnd = 0xFF555555)
        )

        dataStoreManager.saveCategories(categories)

        // When - Search for "Work" (simulating MainScreen category search)
        val savedCategories = dataStoreManager.categoriesFlow.first()
        val searchQuery = "Work"
        val searchResults = savedCategories.filter { category ->
            !category.isDeleted &&
                    category.name.contains(searchQuery, ignoreCase = true)
        }

        // Then - Should find 1 category
        assertEquals(1, searchResults.size)
        assertEquals("Work Projects", searchResults[0].name)
    }

    @Test
    fun testSearchCategoryPartialMatch_shouldFind() = runBlocking {
        // Given - Categories with similar names
        val categories = listOf(
            Category(id = "cat_001", name = "Development", gradientStart = 0xFF000000, gradientEnd = 0xFF111111),
            Category(id = "cat_002", name = "Developer Tools", gradientStart = 0xFF222222, gradientEnd = 0xFF333333),
            Category(id = "cat_003", name = "Design", gradientStart = 0xFF444444, gradientEnd = 0xFF555555)
        )

        dataStoreManager.saveCategories(categories)

        // When - Search for "Dev"
        val savedCategories = dataStoreManager.categoriesFlow.first()
        val searchQuery = "Dev"
        val searchResults = savedCategories.filter { category ->
            !category.isDeleted &&
                    category.name.contains(searchQuery, ignoreCase = true)
        }

        // Then - Should find 2 categories
        assertEquals(2, searchResults.size)
        assertTrue(searchResults.any { it.name == "Development" })
        assertTrue(searchResults.any { it.name == "Developer Tools" })
    }

    @Test
    fun testSearchMultipleKeywords_shouldFindAll() = runBlocking {
        // Given - Notes with various content
        val notes = listOf(
            Note(id = "note_001", categoryId = "cat_001", title = "Kotlin Tutorial", content = "Learn Kotlin basics", isDeleted = false),
            Note(id = "note_002", categoryId = "cat_001", title = "Java Guide", content = "Java programming", isDeleted = false),
            Note(id = "note_003", categoryId = "cat_001", title = "Android Tips", content = "Kotlin for Android", isDeleted = false)
        )

        dataStoreManager.saveNotes(notes)

        // When - Search for "Kotlin"
        val savedNotes = dataStoreManager.notesFlow.first()
        val searchQuery = "Kotlin"
        val searchResults = savedNotes.filter { note ->
            !note.isDeleted && !note.isArchived &&
                    (note.title.contains(searchQuery, ignoreCase = true) ||
                            note.content.contains(searchQuery, ignoreCase = true))
        }

        // Then - Should find 2 notes (title and content matches)
        assertEquals(2, searchResults.size)
        assertTrue(searchResults.any { it.title == "Kotlin Tutorial" })
        assertTrue(searchResults.any { it.title == "Android Tips" })
    }

    @Test
    fun testSearchRealtime_shouldUpdateImmediately() = runBlocking {
        // Given - Initial set of notes
        val notes = listOf(
            Note(id = "note_001", categoryId = "cat_001", title = "First Note", isDeleted = false),
            Note(id = "note_002", categoryId = "cat_001", title = "Second Note", isDeleted = false)
        )

        dataStoreManager.saveNotes(notes)

        // When - Search for "First"
        val savedNotes1 = dataStoreManager.notesFlow.first()
        val searchQuery1 = "First"
        val searchResults1 = savedNotes1.filter { note ->
            !note.isDeleted && !note.isArchived &&
                    (note.title.contains(searchQuery1, ignoreCase = true) ||
                            note.content.contains(searchQuery1, ignoreCase = true))
        }

        // Then - Should find 1 note
        assertEquals(1, searchResults1.size)

        // When - Change search query to "Second" (simulating real-time update)
        val searchQuery2 = "Second"
        val searchResults2 = savedNotes1.filter { note ->
            !note.isDeleted && !note.isArchived &&
                    (note.title.contains(searchQuery2, ignoreCase = true) ||
                            note.content.contains(searchQuery2, ignoreCase = true))
        }

        // Then - Should immediately find different note
        assertEquals(1, searchResults2.size)
        assertEquals("Second Note", searchResults2[0].title)
    }

    @Test
    fun testSearchWithSpecialCharacters_shouldHandle() = runBlocking {
        // Given - Note with special characters
        val note = Note(
            id = "note_001",
            categoryId = "cat_001",
            title = "C++ Programming",
            content = "Learn C++ basics & advanced topics",
            isDeleted = false
        )

        dataStoreManager.saveNotes(listOf(note))

        // When - Search for "C++"
        val savedNotes = dataStoreManager.notesFlow.first()
        val searchQuery = "C++"
        val searchResults = savedNotes.filter { n ->
            !n.isDeleted && !n.isArchived &&
                    (n.title.contains(searchQuery, ignoreCase = true) ||
                            n.content.contains(searchQuery, ignoreCase = true))
        }

        // Then - Should find the note
        assertEquals(1, searchResults.size)
        assertEquals("C++ Programming", searchResults[0].title)
    }

    @Test
    fun testSearchFilteredByCategory_shouldOnlySearchInCategory() = runBlocking {
        // Given - Notes in different categories
        val notes = listOf(
            Note(id = "note_001", categoryId = "cat_work", title = "Work Meeting", isDeleted = false),
            Note(id = "note_002", categoryId = "cat_personal", title = "Personal Meeting", isDeleted = false),
            Note(id = "note_003", categoryId = "cat_work", title = "Work Report", isDeleted = false)
        )

        dataStoreManager.saveNotes(notes)

        // When - Search for "Meeting" in "cat_work" category only
        val savedNotes = dataStoreManager.notesFlow.first()
        val searchQuery = "Meeting"
        val selectedCategoryId = "cat_work"
        val searchResults = savedNotes.filter { note ->
            note.categoryId == selectedCategoryId &&
                    !note.isDeleted && !note.isArchived &&
                    (note.title.contains(searchQuery, ignoreCase = true) ||
                            note.content.contains(searchQuery, ignoreCase = true))
        }

        // Then - Should only find 1 note in cat_work
        assertEquals(1, searchResults.size)
        assertEquals("Work Meeting", searchResults[0].title)
        assertEquals("cat_work", searchResults[0].categoryId)
    }
}