package com.securityc4po.api.pentest

import com.github.tomakehurst.wiremock.common.Json
import com.securityc4po.api.BaseDocumentationIntTest
import com.securityc4po.api.configuration.NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR
import com.securityc4po.api.configuration.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE
import com.securityc4po.api.configuration.SIC_INNER_SHOULD_BE_STATIC
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.data.mongodb.core.query.Query
import org.springframework.restdocs.operation.preprocess.Preprocessors
import org.springframework.restdocs.payload.JsonFieldType
import org.springframework.restdocs.payload.PayloadDocumentation
import org.springframework.restdocs.request.RequestDocumentation
import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation

@SuppressFBWarnings(
    SIC_INNER_SHOULD_BE_STATIC,
    NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR,
    RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE
)
class PentestControllerDocumentationTest : BaseDocumentationIntTest() {

    @Autowired
    lateinit var mongoTemplate: MongoTemplate

    @BeforeEach
    fun init() {
        configureAdminToken()
        persistBasicTestScenario()
    }

    @AfterEach
    fun destroy() {
        cleanUp()
    }

    @Nested
    inner class GetPentests {
        @Test
        fun getPentestsByProjectIdAndCategory() {
            val projectId = "d2e126ba-f608-11ec-b939-0242ac120002"
            val category = "INFORMATION_GATHERING"
            webTestClient.get()
                .uri("/pentests?projectId={projectId}&category={category}", projectId, category)
                .header("Authorization", "Bearer $tokenAdmin")
                .exchange()
                .expectStatus().isOk
                .expectHeader().doesNotExist("")
                .expectBody().json(Json.write(getProjectsResponse()))
                .consumeWith(
                    WebTestClientRestDocumentation.document(
                        "{methodName}",
                        Preprocessors.preprocessRequest(
                            Preprocessors.prettyPrint(),
                            Preprocessors.modifyUris().removePort(),
                            Preprocessors.removeHeaders("Host", "Content-Length")
                        ),
                        Preprocessors.preprocessResponse(
                            Preprocessors.prettyPrint()
                        ),
                        RequestDocumentation.relaxedRequestParameters(
                            RequestDocumentation.parameterWithName("projectId").description("The id of the project you want to get the pentests for"),
                            RequestDocumentation.parameterWithName("category").description("The category you want to get the pentests for")
                        ),
                        PayloadDocumentation.relaxedResponseFields(
                            PayloadDocumentation.fieldWithPath("[].id").type(JsonFieldType.STRING)
                                .description("The id of the requested pentest"),
                            PayloadDocumentation.fieldWithPath("[].projectId").type(JsonFieldType.STRING)
                                .description("The id of the project of the requested pentest"),
                            PayloadDocumentation.fieldWithPath("[].category").type(JsonFieldType.STRING)
                                .description("The category of the requested pentest"),
                            PayloadDocumentation.fieldWithPath("[].title").type(JsonFieldType.STRING)
                                .description("The title of the requested pentest"),
                            PayloadDocumentation.fieldWithPath("[].refNumber").type(JsonFieldType.STRING)
                                .description("The reference number of the requested pentest according to the current OWASP Testing Guide"),
                            PayloadDocumentation.fieldWithPath("[].status").type(JsonFieldType.STRING)
                                .description("The status of the requested pentest"),
                            PayloadDocumentation.fieldWithPath("[].findingIds").type(JsonFieldType.ARRAY)
                                .description("List of ids of the findings in the requested pentest"),
                            PayloadDocumentation.fieldWithPath("[].commentIds").type(JsonFieldType.ARRAY)
                                .description("List of ids of the comments of the requested pentest")
                        )
                    )
                )
        }

        private val pentestOne = Pentest(
            id = "9c8af320-f608-11ec-b939-0242ac120002",
            projectId = "d2e126ba-f608-11ec-b939-0242ac120002",
            category = PentestCategory.INFORMATION_GATHERING,
            title = "Search engine discovery/reconnaissance",
            refNumber = "OTG-INFO-001",
            status = PentestStatus.NOT_STARTED,
            findingIds = emptyList(),
            commentIds = emptyList()
        )
        private val pentestTwo = Pentest(
            id = "43fbc63c-f624-11ec-b939-0242ac120002",
            projectId = "d2e126ba-f608-11ec-b939-0242ac120002",
            category = PentestCategory.INFORMATION_GATHERING,
            title = "Fingerprint Web Server",
            refNumber = "OTG-INFO-002",
            status = PentestStatus.REPORTED,
            findingIds = emptyList(),
            commentIds = emptyList()
        )

        private fun getProjectsResponse() = listOf(
            pentestOne.toPentestResponseBody(),
            pentestTwo.toPentestResponseBody()
        )
    }

    private fun persistBasicTestScenario() {
        // setup test data
        val pentestOne = Pentest(
            id = "9c8af320-f608-11ec-b939-0242ac120002",
            projectId = "d2e126ba-f608-11ec-b939-0242ac120002",
            category = PentestCategory.INFORMATION_GATHERING,
            title = "Search engine discovery/reconnaissance",
            refNumber = "OTG-INFO-001",
            status = PentestStatus.NOT_STARTED,
            findingIds = emptyList(),
            commentIds = emptyList()
        )
        val pentestTwo = Pentest(
            id = "43fbc63c-f624-11ec-b939-0242ac120002",
            projectId = "d2e126ba-f608-11ec-b939-0242ac120002",
            category = PentestCategory.INFORMATION_GATHERING,
            title = "Fingerprint Web Server",
            refNumber = "OTG-INFO-002",
            status = PentestStatus.REPORTED,
            findingIds = emptyList(),
            commentIds = emptyList()
        )
        val pentestThree = Pentest(
            id = "74eae112-f62c-11ec-b939-0242ac120002",
            projectId = "6fad3474-fc29-49f9-bd37-e039e9e60c18",
            category = PentestCategory.AUTHENTICATION_TESTING,
            title = "Testing for Credentials Transported over an Encrypted Channel",
            refNumber = "OTG-AUTHN-001",
            status = PentestStatus.CHECKED,
            findingIds = emptyList(),
            commentIds = emptyList()
        )
        // persist test data in database
        mongoTemplate.save(PentestEntity(pentestOne))
        mongoTemplate.save(PentestEntity(pentestTwo))
        mongoTemplate.save(PentestEntity(pentestThree))
    }

    private fun configureAdminToken() {
        tokenAdmin = getAccessToken("test_admin", "test", "c4po_local", "c4po_realm_local")
    }

    private fun cleanUp() {
        mongoTemplate.findAllAndRemove(Query(), PentestEntity::class.java)

        tokenAdmin = "n/a"
    }
}