package uk.gov.pay.api.it.directdebit;

import com.google.gson.Gson;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import junitparams.converters.Nullable;
import org.apache.http.HttpStatus;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import uk.gov.pay.api.model.directdebit.mandates.MandateConnectorRequest;
import uk.gov.pay.api.model.directdebit.mandates.MandateState;
import uk.gov.pay.api.resources.directdebit.DirectDebitResourceITBase;
import uk.gov.pay.commons.validation.DateTimeUtils;

import javax.ws.rs.core.HttpHeaders;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import static io.restassured.RestAssured.given;
import static io.restassured.http.ContentType.JSON;
import static javax.ws.rs.core.HttpHeaders.AUTHORIZATION;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.core.Is.is;
import static uk.gov.pay.api.model.TokenPaymentType.DIRECT_DEBIT;
import static uk.gov.pay.api.utils.Urls.directDebitFrontendSecureUrl;
import static uk.gov.pay.api.utils.Urls.mandateLocationFor;
import static uk.gov.pay.api.utils.mocks.CreateMandateRequestParams.CreateMandateRequestParamsBuilder.aCreateMandateRequestParams;
import static uk.gov.pay.commons.model.ApiResponseDateTimeFormatter.ISO_INSTANT_MILLISECOND_PRECISION;
import static uk.gov.pay.commons.testing.matchers.HamcrestMatchers.optionalMatcher;

@RunWith(JUnitParamsRunner.class)
public class CreateMandateIT extends DirectDebitResourceITBase {

    private static final ZonedDateTime TIMESTAMP = DateTimeUtils.toUTCZonedDateTime("2016-01-01T12:00:00Z").get();
    private static final String CREATED_DATE = ISO_INSTANT_MILLISECOND_PRECISION.format(TIMESTAMP);
    private static final String CHARGE_TOKEN_ID = "token_1234567asdf";
    private static final String MANDATE_ID = "mandateId";
    private static final String SERVICE_REFERENCE = "test_service_reference";
    private static final String RETURN_URL = "https://service-name.gov.uk/transactions/12345";

    private static String createMandatePayload = new Gson().toJson(Map.of(
            "return_url", RETURN_URL, 
            "reference", "test service reference"));

    @Before
    public void setup() {
        publicAuthMockClient.mapBearerTokenToAccountId(API_KEY, GATEWAY_ACCOUNT_ID, DIRECT_DEBIT);
    }

    @Test
    public void returnUrlLengthValidationFailure() {
        String returnUrl = "https://example.com?something=" + "aVeryLongString12345".repeat(100);

        given().port(app.getLocalPort())
                .body(Map.of("return_url", returnUrl, "reference", "test reference"))
                .accept(JSON)
                .contentType(JSON)
                .header(AUTHORIZATION, "Bearer " + API_KEY)
                .post("/v1/directdebit/mandates")
                .then()
                .statusCode(HttpStatus.SC_UNPROCESSABLE_ENTITY)
                .body("size()", is(3))
                .body("field", is("return_url"))
                .body("code", is("P0102"))
                .body("description", is("Invalid attribute value: return_url. Must be less than or equal to 2000 characters length"));
    }
    
    @Test
    @Parameters({
            "null, Missing mandatory attribute: return_url, P0101",
            "invalidUrl, Invalid attribute value: return_url. Must be a valid URL format, P0102",
            "http://notsecure.com, Invalid attribute value: return_url. Must be a valid URL format, P0102"
    })
    public void returnUrlValidationFailures(@Nullable String returnUrl, String expectedErrorMessage, String errorCode) {

        Map<String, String> payload = new HashMap<>();
        payload.put("reference", "test reference");
        Optional.ofNullable(returnUrl).ifPresent(x -> payload.put("return_url", x));

        given().port(app.getLocalPort())
                .body(payload)
                .accept(JSON)
                .contentType(JSON)
                .header(AUTHORIZATION, "Bearer " + API_KEY)
                .post("/v1/directdebit/mandates")
                .then()
                .statusCode(HttpStatus.SC_UNPROCESSABLE_ENTITY)
                .body("size()", is(3))
                .body("field", is("return_url"))
                .body("code", is(errorCode))
                .body("description", is(expectedErrorMessage));
    }

    @Test
    @Parameters({
            "null, null, Missing mandatory attribute: reference, reference, P0101",
            "null, , Missing mandatory attribute: reference, reference, P0101",
            "null, test reference more than 255 chars test reference more than 255 chars test reference more than 255 " +
                    "chars test reference more than 255 chars test reference more than 255 chars test reference more " +
                    "than 255 chars test reference more than 255 chars test reference more than 255 chars, " +
                    "Invalid attribute value: reference. Must be less than or equal to 255 characters length, reference, P0102",
            " , test reference, Invalid attribute value: description. Must have a size between 1 and 255, description, P0102",
    })
    public void referenceAndDescriptionValidationFailures(@Nullable String description,
                                                @Nullable String serviceReference,
                                                String expectedErrorMessage,
                                                String field,
                                                String errorCode) {
        
        Map<String, String> payload = new HashMap<>();
        payload.put("return_url", "https://example.com");
        Optional.ofNullable(serviceReference).ifPresent(x -> payload.put("reference", x));
        Optional.ofNullable(description).ifPresent(x -> payload.put("description", x));

        given().port(app.getLocalPort())
                .body(payload)
                .accept(JSON)
                .contentType(JSON)
                .header(AUTHORIZATION, "Bearer " + API_KEY)
                .post("/v1/directdebit/mandates")
                .then()
                .statusCode(HttpStatus.SC_UNPROCESSABLE_ENTITY)
                .body("size()", is(3))
                .body("field", is(field))
                .body("code", is(errorCode))
                .body("description", is(expectedErrorMessage));
    }

    @Test
    @Parameters({"I'ma need space I'ma I'ma need space (N-A-S-A)", "null"})
    public void createSuccessfully(@Nullable String description) throws Exception {

        connectorDDMockClient.respondOk_whenCreateMandateRequest(aCreateMandateRequestParams()
                .withMandateId(MANDATE_ID)
                .withServiceReference(SERVICE_REFERENCE)
                .withReturnUrl(RETURN_URL)
                .withCreatedDate(CREATED_DATE)
                .withState(new MandateState("created", false))
                .withGatewayAccountId(GATEWAY_ACCOUNT_ID)
                .withDescription(description)
                .withChargeTokenId(CHARGE_TOKEN_ID).build());

        Map<String, String> payload = new HashMap<>();
        payload.put("return_url", RETURN_URL);
        payload.put("reference", SERVICE_REFERENCE);
        Optional.ofNullable(description).ifPresent(x -> payload.put("description", x));

        given().port(app.getLocalPort())
                .body(payload)
                .accept(JSON)
                .contentType(JSON)
                .header(AUTHORIZATION, "Bearer " + API_KEY)
                .post("/v1/directdebit/mandates")
                .then()
                .statusCode(201)
                .contentType(JSON)
                .header(HttpHeaders.LOCATION, is("http://publicapi.url/v1/directdebit/mandates/" + MANDATE_ID))
                .body("mandate_id", is(MANDATE_ID))
                .body("reference", is(SERVICE_REFERENCE))
                .body("description", optionalMatcher(description))
                .body("provider_id", is(nullValue()))
                .body("return_url", is(RETURN_URL))
                .body("created_date", is(CREATED_DATE))
                .body("payment_provider", is("gocardless"))
                .body("state.status", is("created"))
                .body("_links.self.href", is(mandateLocationFor(MANDATE_ID)))
                .body("_links.self.method", is("GET"))
                .body("_links.next_url.href", is(directDebitFrontendSecureUrl() + CHARGE_TOKEN_ID))
                .body("_links.next_url.method", is("GET"))
                .body("_links.next_url_post.href", is(directDebitFrontendSecureUrl()))
                .body("_links.next_url_post.method", is("POST"))
                .body("_links.next_url_post.type", is("application/x-www-form-urlencoded"))
                .body("_links.next_url_post.params.chargeTokenId", is(CHARGE_TOKEN_ID))
                .body("_links.payments.href", is("http://publicapi.url/v1/directdebit/payments?mandate_id=" + MANDATE_ID))
                .body("_links.payments.method", is("GET"));

        connectorDDMockClient.verifyCreateMandateConnectorRequest(
                new MandateConnectorRequest(RETURN_URL, SERVICE_REFERENCE, description), GATEWAY_ACCOUNT_ID);
    }

    @Test
    public void respondWith500_whenConnectorResponseIsAnUnrecognisedError() {

        String errorMessage = "something went wrong";

        connectorDDMockClient.respondBadRequest_whenCreateAgreementRequest(GATEWAY_ACCOUNT_ID, errorMessage);

        given().port(app.getLocalPort())
                .body(createMandatePayload)
                .accept(JSON)
                .contentType(JSON)
                .header(AUTHORIZATION, "Bearer " + API_KEY)
                .post("/v1/directdebit/mandates")
                .then()
                .statusCode(500)
                .contentType(JSON)
                .body("code", is("P0198"))
                .body("description", is("Downstream system error"))
                .extract().body().asString();
    }


    @Test
    public void respondWith500_whenConnectorResponseIsGCAccountNotLinked() {

        String errorMessage = "something went wrong";

        connectorDDMockClient.respondWithGCAccountNotLinked_whenCreateMandateRequest(GATEWAY_ACCOUNT_ID, errorMessage);

        given().port(app.getLocalPort())
                .body(createMandatePayload)
                .accept(JSON)
                .contentType(JSON)
                .header(AUTHORIZATION, "Bearer " + API_KEY)
                .post("/v1/directdebit/mandates")
                .then()
                .statusCode(500)
                .contentType(JSON)
                .body("code", is("P0199"))
                .body("description", is("There is an error with this account. Please contact support"))
                .extract().body().asString();
    }
}