package com.r3corda.contracts

import com.r3corda.core.contracts.DOLLARS
import com.r3corda.core.contracts.`issued by`
import com.r3corda.core.serialization.OpaqueBytes
import com.r3corda.core.utilities.DUMMY_PUBKEY_1
import com.r3corda.core.utilities.TEST_TX_TIME
import com.r3corda.testing.*
import org.junit.Test
import java.time.LocalDate
import java.util.*
import kotlin.test.fail

class InvoiceTests {

    val defaultRef = OpaqueBytes(ByteArray(1, { 1 }))
    val defaultIssuer = MEGA_CORP.ref(defaultRef)

    val invoiceProperties = Invoice.InvoiceProperties(
                            invoiceID = "123",
                            seller = LocDataStructures.Company(
                                    name = "Mega Corp LTD.",
                                    address = "123 Main St. Awesome Town, ZZ 11111",
                                    phone = null
                            ),
                            buyer = LocDataStructures.Company(
                                    name = "Sandworm Imports",
                                    address = "555 Elm St. Little Town, VV, 22222",
                                    phone = null
                            ),
                            invoiceDate = LocalDate.now(),
                            term = 60,
                            goods = arrayListOf<LocDataStructures.PricedGood>(
                                    LocDataStructures.PricedGood(
                                            description = "Salt",
                                            purchaseOrderRef = null,
                                            quantity = 10,
                                            unitPrice = 3.DOLLARS `issued by` defaultIssuer,
                                            grossWeight = null
                                    ),
                                    LocDataStructures.PricedGood(
                                            description = "Pepper",
                                            purchaseOrderRef = null,
                                            quantity = 20,
                                            unitPrice = 4.DOLLARS `issued by` defaultIssuer,
                                            grossWeight = null
                                    )
                            )
                    )
    val initialInvoiceState = Invoice.State(MEGA_CORP, ALICE, false,invoiceProperties)

    @Test
    fun `Issue - requireThat Tests`() {

        //Happy Path Issue
        transaction {
            output { initialInvoiceState }
            command(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() }
            timestamp(TEST_TX_TIME)
            verifies()
        }

        transaction {
            input { initialInvoiceState }
            output { initialInvoiceState }
            command(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() }
            timestamp(TEST_TX_TIME)
            this `fails with` "there is no input state"
        }

        transaction {
            output { initialInvoiceState }
            command(DUMMY_PUBKEY_1) { Invoice.Commands.Issue() }
            timestamp(TEST_TX_TIME)
            this `fails with` "the transaction is signed by the invoice owner"
        }

        var props = invoiceProperties.copy(seller = invoiceProperties.buyer)
        transaction {
            output { initialInvoiceState.copy(props = props) }
            command(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() }
            timestamp(TEST_TX_TIME)
            this `fails with` "the buyer and seller must be different"
        }

        transaction {
            output { initialInvoiceState.copy(assigned = true) }
            command(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() }
            timestamp(TEST_TX_TIME)
            this `fails with` "the invoice must not be assigned"
        }

        props = invoiceProperties.copy(invoiceID = "")
        transaction {
            output { initialInvoiceState.copy(props = props) }
            command(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() }
            timestamp(TEST_TX_TIME)
            this `fails with` "the invoice ID must not be blank"
        }

        val withMessage = "the term must be a positive number"
        val r = try {
            props = invoiceProperties.copy(term = 0)
            false
        } catch (e: Exception) {
            val m = e.message
            if (m == null)
                fail("Threw exception without a message")
            else
                if (!m.toLowerCase().contains(withMessage.toLowerCase())) throw AssertionError("Error was actually: $m", e)
            true
        }
        if (!r) throw AssertionError("Expected exception but didn't get one")

        props = invoiceProperties.copy(invoiceDate = LocalDate.now().minusDays(invoiceProperties.term + 1))
        transaction {
            output { initialInvoiceState.copy(props = props) }
            command(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() }
            timestamp(java.time.Instant.now())
            this `fails with` "the payment date must be in the future"
        }

        val withMessage2 = "there must be goods assigned to the invoice"
        val r2 = try {
            props = invoiceProperties.copy(goods = Collections.emptyList())
            false
        } catch (e: Exception) {
            val m = e.message
            if (m == null)
                fail("Threw exception without a message")
            else
                if (!m.toLowerCase().contains(withMessage2.toLowerCase())) {
                    throw AssertionError("Error was actually: $m expected $withMessage2", e)
                }
            true
        }
        if (!r2) throw AssertionError("Expected exception but didn't get one")

       val goods = arrayListOf<LocDataStructures.PricedGood>( LocDataStructures.PricedGood(
                    description = "Salt",
                    purchaseOrderRef = null,
                    quantity = 10,
                    unitPrice = 0.DOLLARS `issued by` defaultIssuer,
                    grossWeight = null
                ))

        props = invoiceProperties.copy(goods = goods)
        transaction {
            output { initialInvoiceState.copy(props = props) }
            command(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() }
            timestamp(TEST_TX_TIME)
            this `fails with` "the invoice amount must be non-zero"
        }
    }

    @Test
    fun `Assign - requireThat Tests`() {

        //Happy Path Assign
        transaction {
            input { initialInvoiceState }
            output { initialInvoiceState.copy(assigned = true) }
            command(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() }
            timestamp(TEST_TX_TIME)
            verifies()
        }

        transaction {
            input { initialInvoiceState }
            output { initialInvoiceState.copy(owner = ALICE) }
            command(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() }
            timestamp(TEST_TX_TIME)
            this `fails with` "input state owner must be the same as the output state owner"
        }

        transaction {
            input { initialInvoiceState }
            output { initialInvoiceState }
            command(DUMMY_PUBKEY_1) { Invoice.Commands.Assign() }
            timestamp(TEST_TX_TIME)
            this `fails with` "the transaction must be signed by the owner"
        }

        var props = invoiceProperties.copy(seller = invoiceProperties.buyer)
        transaction {
            input { initialInvoiceState }
            output { initialInvoiceState.copy(props = props) }
            command(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() }
            timestamp(TEST_TX_TIME)
            this `fails with` "the invoice properties must remain unchanged"
        }

        transaction {
            input { initialInvoiceState.copy(assigned = true) }
            output { initialInvoiceState }
            command(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() }
            timestamp(TEST_TX_TIME)
            this `fails with` "the input invoice must not be assigned"
        }

        transaction {
            input { initialInvoiceState }
            output { initialInvoiceState.copy(assigned = false) }
            command(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() }
            timestamp(TEST_TX_TIME)
            this `fails with` "the output invoice must be assigned"
        }

        props = invoiceProperties.copy(invoiceDate = LocalDate.now().minusDays(invoiceProperties.term + 1))
        transaction {
            input { initialInvoiceState.copy(props = props) }
            output { initialInvoiceState.copy(props = props, assigned = true) }
            command(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() }
            timestamp(java.time.Instant.now())
            this `fails with` "the payment date must be in the future"
        }
    }

    @Test
    fun `Extinguish - requireThat Tests`() {

        //Happy Path Extinguish
        val props = invoiceProperties.copy(invoiceDate = LocalDate.now().minusDays(invoiceProperties.term + 1))
        transaction {
            input { initialInvoiceState.copy(props = props) }
            command(MEGA_CORP_PUBKEY) { Invoice.Commands.Extinguish() }
            timestamp(java.time.Instant.now())
            verifies()
        }

        transaction {
            input { initialInvoiceState }
            output { initialInvoiceState }
            command(MEGA_CORP_PUBKEY) { Invoice.Commands.Extinguish() }
            timestamp(java.time.Instant.now())
            this `fails with` "there shouldn't be an output state"
        }

        transaction {
            input { initialInvoiceState }
            command(DUMMY_PUBKEY_1) { Invoice.Commands.Extinguish() }
            timestamp(java.time.Instant.now())
            this `fails with` "the transaction must be signed by the owner"
        }

//        transaction {
//            input { initialInvoiceState }
//            command(MEGA_CORP_PUBKEY) { Invoice.Commands.Extinguish() }
//            timestamp(java.time.Instant.now())
//            this `fails requirement` "the payment date must be today or in the past"
//        }
    }
}