package com.ankurm.camel;

import org.apache.camel.CamelContext;
import org.apache.camel.EndpointInject;
import org.apache.camel.Produce;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.spring.junit6.CamelSpringBootTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.math.BigDecimal;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * The route's real terminal endpoints are property placeholders
 * ({{camel.route.priority-out}} and friends); src/test/resources'
 * application.properties overrides them to "mock:" endpoints, so this
 * test observes the route's actual behaviour without AdviceWith and
 * without the main application.properties' "log:" endpoints ever
 * running during a test.
 *
 * Spring caches one ApplicationContext (and so one CamelContext, and so
 * the same MockEndpoint instances) across every @Test method here. A
 * first version of this class had no @BeforeEach reset and failed with
 * "mock://priorityOut Received message count. Expected: <0> but was:
 * <1>" on the second test to run - the message the first test sent was
 * still sitting in that mock's history. MockEndpoint.resetMocks(context)
 * clears every mock endpoint's received-message state before each test.
 */
@CamelSpringBootTest
@SpringBootTest(classes = CamelDemoApplication.class)
class OrderRoutesTest {

    @Autowired
    CamelContext camelContext;

    @Produce("direct:orders")
    ProducerTemplate producer;

    @EndpointInject("mock:priorityOut")
    MockEndpoint priorityOut;

    @EndpointInject("mock:standardOut")
    MockEndpoint standardOut;

    @EndpointInject("mock:deadLetterOut")
    MockEndpoint deadLetterOut;

    @Autowired
    OrderProcessor orderProcessor;

    @BeforeEach
    void resetMockEndpoints() {
        MockEndpoint.resetMocks(camelContext);
    }

    @Test
    void priorityOrderIsRoutedToThePriorityEndpoint() throws Exception {
        priorityOut.expectedMessageCount(1);
        standardOut.expectedMessageCount(0);

        producer.sendBodyAndHeader(
                new Order("ord-1", "PRIORITY", new BigDecimal("250.00")),
                "orderType", "PRIORITY");

        priorityOut.assertIsSatisfied();
        standardOut.assertIsSatisfied();

        Transcript t = new Transcript("30-content-based-routing.txt");
        t.line("orderType=PRIORITY -> routed to {{camel.route.priority-out}} (mock:priorityOut), 1 message");
        t.line("standardOut received 0 messages for this exchange");
        t.write();
    }

    @Test
    void standardOrderIsRoutedToTheStandardEndpoint() throws Exception {
        standardOut.expectedMessageCount(1);
        priorityOut.expectedMessageCount(0);

        producer.sendBodyAndHeader(
                new Order("ord-2", "STANDARD", new BigDecimal("40.00")),
                "orderType", "STANDARD");

        standardOut.assertIsSatisfied();
        priorityOut.assertIsSatisfied();

        Transcript t = new Transcript("30-standard-routing.txt");
        t.line("orderType=STANDARD -> routed to {{camel.route.standard-out}} (mock:standardOut), 1 message");
        t.line("priorityOut received 0 messages for this exchange");
        t.write();
    }

    @Test
    void invalidOrderIsRetriedTwiceThenRoutedToTheDeadLetterEndpoint() throws Exception {
        int attemptsBefore = orderProcessor.attemptCount();
        deadLetterOut.expectedMessageCount(1);

        producer.sendBodyAndHeader(
                new Order("ord-3", "STANDARD", new BigDecimal("-5.00")),
                "orderType", "STANDARD");

        deadLetterOut.assertIsSatisfied();
        int attemptsAfter = orderProcessor.attemptCount();

        Transcript t = new Transcript("30-dead-letter-retry.txt");
        t.line("order ord-3 amount=-5.00 -> IllegalArgumentException on every attempt");
        t.line("maximumRedeliveries(2) -> processOrder actually invoked %d times (1 original + 2 redeliveries)",
                attemptsAfter - attemptsBefore);
        t.line("after redeliveries exhausted -> handled(true), routed to {{camel.route.dead-letter-out}} (mock:deadLetterOut), 1 message");
        t.write();

        assertThat(attemptsAfter - attemptsBefore).isEqualTo(3);
    }
}
