package com.securityc4po.api.pentest

import com.github.tomakehurst.wiremock.common.Json
import com.securityc4po.api.BaseIntTest
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 com.securityc4po.api.project.Project
import com.securityc4po.api.project.ProjectEntity
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.boot.web.server.LocalServerPort
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.data.mongodb.core.query.Query
import org.springframework.test.web.reactive.server.WebTestClient
import reactor.core.publisher.Mono
import java.time.Duration

@SuppressFBWarnings(
    SIC_INNER_SHOULD_BE_STATIC,
    NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR,
    RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE
)
class PentestControllerIntTest : BaseIntTest() {

    @LocalServerPort
    private var port = 0

    @Autowired
    lateinit var mongoTemplate: MongoTemplate

    @Autowired
    private lateinit var webTestClient: WebTestClient

    @BeforeEach
    fun setupWebClient() {
        webTestClient = WebTestClient.bindToServer()
            .baseUrl("http://localhost:$port")
            .responseTimeout(Duration.ofMillis(10000))
            .build()
    }

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

    @AfterEach
    fun destroy() {
        cleanUp()
    }

    @Nested
    inner class GetPentests {
        @Test
        fun `requesting pentests by projectId and category successfully`() {
            val projectId = "d2e126ba-f608-11ec-b939-0242ac120025"
            val category = "INFORMATION_GATHERING"
            webTestClient.get()
                .uri("/pentests?projectId={projectId}&category={category}", projectId, category)
                .header("Authorization", "Bearer $tokenAdmin")
                .exchange()
                .expectStatus().isOk
                .expectHeader().valueEquals("Application-Name", "SecurityC4PO")
                .expectBody().json(Json.write(getPentests()))
        }

        private val pentestOne = Pentest(
            id = "9c8af320-f608-11ec-b939-0242ac120002",
            projectId = "d2e126ba-f608-11ec-b939-0242ac120025",
            category = PentestCategory.INFORMATION_GATHERING,
            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-0242ac120025",
            category = PentestCategory.INFORMATION_GATHERING,
            refNumber = "OTG-INFO-002",
            status = PentestStatus.IN_PROGRESS,
            findingIds = emptyList(),
            commentIds = emptyList()
        )

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

    @Nested
    inner class SavePentest {
        @Test
        fun `save pentest successfully`() {
            val projectId = "d2e126ba-f608-11ec-b939-0242ac120025"
            webTestClient.post()
                .uri("/pentests/{projectId}", projectId)
                .header("Authorization", "Bearer $tokenAdmin")
                .body(Mono.just(pentestRequestBody), PentestRequestBody::class.java)
                .exchange()
                .expectStatus().isAccepted
                .expectHeader().valueEquals("Application-Name", "SecurityC4PO")
                .expectBody()
                .jsonPath("$.projectId").isEqualTo("d2e126ba-f608-11ec-b939-0242ac120025")
                .jsonPath("$.category").isEqualTo("CLIENT_SIDE_TESTING")
                .jsonPath("$.refNumber").isEqualTo("OTG-CLIENT-001")
                .jsonPath("$.status").isEqualTo("IN_PROGRESS")
                .jsonPath("$.findingIds").isEmpty
                .jsonPath("$.commentIds").isEmpty
        }

        val pentestRequestBody = PentestRequestBody(
            projectId = "d2e126ba-f608-11ec-b939-0242ac120025",
            category = "CLIENT_SIDE_TESTING",
            refNumber = "OTG-CLIENT-001",
            status = "IN_PROGRESS",
            findingIds = emptyList<String>(),
            commentIds = emptyList<String>()
        )
    }

    @Nested
    inner class UpdatePentest {
        @Test
        fun `update pentest successfully`() {
            val pentestOneId = "9c8af320-f608-11ec-b939-0242ac120002"
            webTestClient.patch()
                .uri("/pentests/{pentestId}", pentestOneId)
                .header("Authorization", "Bearer $tokenAdmin")
                .body(Mono.just(pentestOneRequestBody), PentestRequestBody::class.java)
                .exchange()
                .expectStatus().isAccepted
                .expectHeader().valueEquals("Application-Name", "SecurityC4PO")
                .expectBody()
                .jsonPath("$.id").isEqualTo(pentestOneId)
                .jsonPath("$.projectId").isEqualTo("d2e126ba-f608-11ec-b939-0242ac120025")
                .jsonPath("$.category").isEqualTo("INFORMATION_GATHERING")
                .jsonPath("$.refNumber").isEqualTo("OTG-INFO-001")
                .jsonPath("$.status").isEqualTo("OPEN")
                .jsonPath("$.findingIds").isEmpty
                .jsonPath("$.commentIds").isEmpty
        }

        val pentestOneRequestBody = PentestRequestBody(
            projectId = "d2e126ba-f608-11ec-b939-0242ac120025",
            category = "INFORMATION_GATHERING",
            refNumber = "OTG-INFO-001",
            status = "OPEN",
            findingIds = emptyList<String>(),
            commentIds = emptyList<String>()
        )
    }

    private fun persistBasicTestScenario() {
        // setup test data
        // project
        val projectOne = Project(
            id = "d2e126ba-f608-11ec-b939-0242ac120025",
            client = "E Corp",
            title = "Some Mock API (v1.0) Scanning",
            createdAt = "2021-01-10T18:05:00Z",
            tester = "Novatester",
            createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
        )
        // pentests
        val pentestOne = Pentest(
            id = "9c8af320-f608-11ec-b939-0242ac120002",
            projectId = "d2e126ba-f608-11ec-b939-0242ac120025",
            category = PentestCategory.INFORMATION_GATHERING,
            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-0242ac120025",
            category = PentestCategory.INFORMATION_GATHERING,
            refNumber = "OTG-INFO-002",
            status = PentestStatus.IN_PROGRESS,
            findingIds = emptyList(),
            commentIds = emptyList()
        )
        val pentestThree = Pentest(
            id = "16vbc63c-f624-11ec-b939-0242ac120002",
            projectId = "d2e126ba-f608-11ec-b939-0242ac120025",
            category = PentestCategory.AUTHENTICATION_TESTING,
            refNumber = "OTG-AUTHN-001",
            status = PentestStatus.COMPLETED,
            findingIds = emptyList(),
            commentIds = emptyList()
        )
        // persist test data in database
        mongoTemplate.save(ProjectEntity(projectOne))
        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(), ProjectEntity::class.java)
        mongoTemplate.findAllAndRemove(Query(), PentestEntity::class.java)

        tokenAdmin = "n/a"
    }
}