package dev.privacyllc.period.feature.settings

import androidx.test.core.app.ApplicationProvider
import dev.privacyllc.period.R
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import java.io.File

/**
 * §4's promise: said in two places, written in one.
 *
 * Onboarding shows it before any data is entered; Settings shows it beside the
 * controls that act on that data. Those are different jobs and both are
 * required — but a second literal is how two versions of a promise come to
 * exist, which is the failure `DOC_TRUST_MAP.md` exists to prevent, here in
 * code rather than in prose.
 *
 * So one test reads the resource and one scans for a copy of it.
 */
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [34])
class PrivacyPromiseTest {

    private val context = ApplicationProvider.getApplicationContext<android.content.Context>()

    private val title: String get() = context.getString(R.string.privacy_promise_title)
    private val body: String get() = context.getString(R.string.privacy_promise_body)

    @Test fun `the promise exists and says the thing it is for`() {
        assertTrue("the title is empty", title.isNotBlank())
        assertTrue(
            "the promise must say what it promises: that the data is never sold",
            body.contains("never sell", ignoreCase = true),
        )
    }

    /**
     * The wording is deliberately narrower than it could be, and that is the
     * part most likely to be "improved" by somebody who has not read why.
     *
     * It promises we never **sell** the data. It does not claim no third party
     * ever processes anything, because Play Billing and an ad SDK eventually
     * will — and a promise the implementation cannot keep is worse than a
     * narrower one that holds. `SECURITY.md` records the same wording and the
     * same reason.
     */
    @Test fun `the promise does not claim more than the app can keep`() {
        val overclaims = listOf(
            "no third party",
            "never shared",
            "nobody else",
            "no one else",
            "never leaves your phone",
            "fully encrypted",
            "end-to-end",
        )
        val found = overclaims.filter { body.contains(it, ignoreCase = true) }
        assertEquals(
            "the promise has been strengthened past what the app can deliver — " +
                "see the comment beside the string resource",
            emptyList<String>(),
            found,
        )
    }

    /**
     * Nobody has re-introduced a literal copy.
     *
     * Scans Kotlin source rather than checking the two known screens, because
     * the failure is a *third* surface pasting the sentence in. The distinctive
     * clause is used rather than the whole paragraph so that a reflowed string
     * still matches.
     */
    @Test fun `the promise appears as a literal in no Kotlin source`() {
        val distinctive = "never sell your personal or health data"

        val sources = File("src/main/kotlin").walkTopDown()
            .filter { it.isFile && it.extension == "kt" }
            .toList()

        // A scan that reads nothing is not a pass — the same refusal the Gradle
        // guards make, for the same reason.
        assertTrue(
            "no Kotlin sources were scanned, so nothing was checked — has the " +
                "module layout moved?",
            sources.size > 10,
        )

        // Comments stripped first — GUARDS.md §2. A KDoc explaining why the
        // promise lives in one place would otherwise contain the sentence it
        // forbids, so the clearest possible comment would fail the test and the
        // obvious fix would be to delete the explanation.
        fun codeOf(text: String): String =
            text.replace(Regex("""/\*.*?\*/""", RegexOption.DOT_MATCHES_ALL), "")
                .lines().joinToString("\n") { it.substringBefore("//") }

        val offenders = sources.filter { codeOf(it.readText()).contains(distinctive, ignoreCase = true) }
            .map { it.name }

        assertEquals(
            "the promise is written out in Kotlin as well as in strings.xml, so " +
                "there are now two copies to keep in step",
            emptyList<String>(),
            offenders,
        )
    }
}
