package ru.forbion.f7cloud.feature.notes

import okhttp3.OkHttpClient
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test

/**
 * Юнит-тесты Notes API v1 через MockWebServer — пишущие сценарии (create/save/If-Match/412)
 * проверяются здесь, НЕ на боевом forbion (mail/073). Покрывают контракт из карты
 * app-map/notes.md: exclude=content, ETag/If-None-Match, If-Match→412 конфликт.
 */
class NotesApiClientTest {

    private lateinit var server: MockWebServer
    private lateinit var api: NotesApiClient

    @Before
    fun setUp() {
        server = MockWebServer()
        server.start()
        api = NotesApiClient(OkHttpClient(), server.url("/").toString().trimEnd('/'))
    }

    @After
    fun tearDown() {
        server.shutdown()
    }

    @Test
    fun `list parses notes and sends exclude=content plus ETag`() {
        val body = """[{"id":7,"etag":"e7","title":"Заметка",""" +
            """"category":"Работа","favorite":true,"modified":100}]"""
        server.enqueue(MockResponse().setHeader("ETag", "coll-1").setBody(body))

        val res = api.list(excludeContent = true, ifNoneMatch = "prev-etag")

        assertFalse(res.notModified)
        assertEquals("coll-1", res.etag)
        assertEquals(1, res.notes.size)
        assertEquals(7L, res.notes[0].id)
        assertEquals("Заметка", res.notes[0].title)
        assertTrue(res.notes[0].favorite)

        val recorded = server.takeRequest()
        assertEquals("/index.php/apps/notes/api/v1/notes?exclude=content", recorded.path)
        assertEquals("prev-etag", recorded.getHeader("If-None-Match"))
    }

    @Test
    fun `list returns notModified on 304`() {
        server.enqueue(MockResponse().setResponseCode(304))

        val res = api.list(ifNoneMatch = "coll-1")

        assertTrue(res.notModified)
        assertTrue(res.notes.isEmpty())
        assertEquals("coll-1", res.etag)
    }

    @Test
    fun `saveContent sends If-Match with quoted etag`() {
        server.enqueue(MockResponse().setBody("""{"id":7,"etag":"e8","content":"new"}"""))

        val saved = api.saveContent(Note(id = 7, etag = "e7", content = "new"))

        assertEquals("e8", saved.etag)
        val recorded = server.takeRequest()
        assertEquals("PUT", recorded.method)
        assertEquals("/index.php/apps/notes/api/v1/notes/7", recorded.path)
        assertEquals("\"e7\"", recorded.getHeader("If-Match"))
        assertTrue(recorded.body.readUtf8().contains("\"content\""))
    }

    @Test
    fun `saveContent on 412 rereads server version and throws conflict`() {
        server.enqueue(MockResponse().setResponseCode(412)) // PUT → конфликт
        server.enqueue(MockResponse().setBody("""{"id":7,"etag":"server","content":"srv"}""")) // GET перечит

        val ex = assertThrows(NoteConflictException::class.java) {
            api.saveContent(Note(id = 7, etag = "stale", content = "mine"))
        }

        assertEquals(7L, ex.noteId)
        assertEquals("server", ex.serverNote?.etag)
        assertEquals("srv", ex.serverNote?.content)
    }

    @Test
    fun `create posts content and category`() {
        server.enqueue(MockResponse().setBody("""{"id":9,"etag":"e9","content":"c","category":"Личное"}"""))

        val note = api.create(content = "c", category = "Личное")

        assertEquals(9L, note.id)
        val recorded = server.takeRequest()
        assertEquals("POST", recorded.method)
        assertEquals("/index.php/apps/notes/api/v1/notes", recorded.path)
        val body = recorded.body.readUtf8()
        assertTrue(body.contains("\"content\""))
        assertTrue(body.contains("Личное"))
    }

    @Test
    fun `setFavorite puts favorite field without If-Match`() {
        server.enqueue(MockResponse().setBody("""{"id":7,"favorite":true}"""))

        val note = api.setFavorite(7, true)

        assertTrue(note.favorite)
        val recorded = server.takeRequest()
        assertEquals("PUT", recorded.method)
        assertNull(recorded.getHeader("If-Match"))
        assertTrue(recorded.body.readUtf8().contains("\"favorite\""))
    }

    @Test
    fun `delete issues DELETE on note id`() {
        server.enqueue(MockResponse().setResponseCode(200))

        api.delete(7)

        val recorded = server.takeRequest()
        assertEquals("DELETE", recorded.method)
        assertEquals("/index.php/apps/notes/api/v1/notes/7", recorded.path)
    }
}
