package cz.moneta.test.dsl.util.task.retail.other;

import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.util.TestUtils;
import cz.moneta.test.dsl.util.data.client.NonClient;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import cz.moneta.test.harness.endpoints.openapi.OpenApiEndpoint;
import cz.moneta.test.harness.support.rest.RawRestRequest;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.MultiPart;
import org.glassfish.jersey.media.multipart.file.StreamDataBodyPart;
import org.junit.Assert;

import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.MediaType;
import java.io.InputStream;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static cz.moneta.test.dsl.util.DateUtils.getDateWithFormat;
import static cz.moneta.test.dsl.util.task.retail.loans.constants.LoansCBLRequests.*;

// Document Uploader
public class DocumentUploaderTasks {
    private static final Logger logger = LogManager.getLogger(WebEndpoint.class);
    private static final int NUMBER_OF_UPLOAD_TRIES = 4;
    private static final int NUMBER_OF_UPLOAD_STATUS_TRIES = 60;
    private static final String IDENTITY_CARD_FRONT = "IDENTITY_CARD_FRONT";
    private static final String IDENTITY_CARD_BACK = "IDENTITY_CARD_BACK";
    private static final String ID_FRONT_RESOURCE = "regression/loans/ID_FRONT.jpeg";
    private static final String ID_BACK_RESOURCE = "regression/loans/ID_BACK.jpg";
    private static final String PASSPORT_RESOURCE = "regression/loans/PASSPORT.jpeg";
    private static final String INVALID_PICTURE_RESOURCE = "regression/loans/invalid_image.jpg";
    private static final String DRIVING_LICENSE_RESOURCE = "regression/loans/RP.jpg";
    private static final String BIRTH_CERTIFICATE_RESOURCE = "regression/loans/BIRTH_CERTIFICATE.jpg";
    private static final String PASSPORT = "PASSPORT";
    private static final String SELFIE = "SELFIE";
    private static final String DRIVING_LICENSE = "DRIVING_LICENSE";
    private static final String BIRTH_CERTIFICATE = "BIRTH_CERTIFICATE";
    private static final String INCOME = "ACKNOWLEDGMENT_OF_RECEIPT";
    private static final String DU_UPLOAD_FAIL_MSG = "DocumentUploader called upload with payload: %s BUT have to take picture again (%s)";
    private static final String DU_UPLOAD_MSG = "DocumentUploader called upload with payload: %s";
    private static final String UPLOAD_PATH = "/unsecured/document/uploader/upload";
    private static final String UPLOAD_STATUS_PATH = "/unsecured/document/uploader/upload-status";
    private static final String LIVENESS_PATH = "/unsecured/document/uploader/liveness/upload";

    private final Harness harness;
    private String xFlowID;
    private String mobileCode;
    private String channelNumber;

    public DocumentUploaderTasks(Harness harness) {
        this.harness = harness;
    }

    public void takeIdentityBE(NonClient nonClient, String channel) {
        mobileCode = harness.get("CODE").toString();
        channelNumber = channel;
        notify(mobileCode);
        takeIdentityCard(nonClient);
        uploadLivenessAndSelfie();
    }

    public void takeIdentityWithPassportBE(NonClient nonClient, String channel) {
        mobileCode = harness.get("CODE").toString();
        channelNumber = channel;
        notify(mobileCode);
        takeIdentityCard(nonClient);
        takePassport(nonClient);
        uploadLivenessAndSelfie();
    }

    public void takeIdentityWithDrivingLicenseBE(NonClient nonClient, String channel) {
        mobileCode = harness.get("CODE").toString();
        channelNumber = channel;
        notify(mobileCode);
        takeIdentityCard(nonClient);
        takeDrivingLicence(nonClient);
        uploadLivenessAndSelfie();
    }

    public void takeIdentityWithOnlyBirthCertificateBE(NonClient childClient, String parent1BirthNumber, String parent2BirthNumber, String channel) {
        mobileCode = harness.get("CODE").toString();
        channelNumber = channel;
        notify(mobileCode);
        takeBirthCertificate(childClient, parent1BirthNumber, parent2BirthNumber);
    }

    public void takeIdentityWithIdAndBirthCertificateBE(NonClient childClient, String parent1BirthNumber, String parent2BirthNumber, String channel) {
        mobileCode = harness.get("CODE").toString();
        channelNumber = channel;
        notify(mobileCode);
        takeBirthCertificate(childClient, parent1BirthNumber, parent2BirthNumber);
        takeIdentityCard(childClient);
    }

    public void takeProofOfIncomeByCBL(String channel) {
        mobileCode = harness.get("CODE").toString();
        channelNumber = channel;
        notify(mobileCode);
        uploadPoP();
        uploadPoPFinished();
    }

    private void upload(String resource, String typeOfDocument, String size) {
        MultiPart multiPart;
        StreamDataBodyPart filePart;
        for (int i = 0; i < NUMBER_OF_UPLOAD_TRIES; i++) {
            multiPart = new MultiPart(MediaType.MULTIPART_FORM_DATA_TYPE);
            filePart = new StreamDataBodyPart("data",
                    Objects.requireNonNull(DocumentUploaderTasks.class.getClassLoader().getResourceAsStream(resource)),
                    typeOfDocument + ".jpg", MediaType.valueOf("image/jpeg"));
            multiPart.getBodyParts().add(new FormDataBodyPart("dataSize", size));
            multiPart.getBodyParts().add(filePart);
            multiPart.getBodyParts().add(new FormDataBodyPart("documentPageType", typeOfDocument));
            sendMultipartData(UPLOAD_PATH, multiPart);
            this.sleep(1);
            if (!hasToRetake(typeOfDocument)) {
                return;
            }
        }
        Assert.fail(String.format("DocumentUploader called maximum number of tries with payload: %s", typeOfDocument));
    }

    private boolean hasToRetake(String typeOfDocument) {
        String status;
        for (int j = 0; j < NUMBER_OF_UPLOAD_STATUS_TRIES; j++) {
            status = prepareRequest(UPLOAD_STATUS_PATH)
                    .withPayload(String.format(STATUS_PAYLOAD, typeOfDocument))
                    .post()
                    .andAssertStatus(200)
                    .andAssertPresent("status")
                    .extract("status")
                    .textValue();
            harness.log(String.format("Upload status %s", status));
            switch (status) {
                case "WRONG_DOC":
                case "LOW_QUALITY":
                    harness.log(String.format(DU_UPLOAD_FAIL_MSG, typeOfDocument, status));
                    return true;
                case "OK":
                    harness.log(String.format(DU_UPLOAD_MSG, typeOfDocument));
                    return false;
                case "IN_PROGRESS":
                    this.sleep(1);
                    break;
                default:
                    Assert.fail("Upload-status ended with error. Check CBLJournal for more information.");
                    break;
            }
        }
        Assert.fail("Upload-status ended with error. Check CBLJournal for more information.");
        return false;
    }

    private void sendMultipartData(String path, MultiPart multiPart) {
        new OpenApiEndpoint(harness).post(
                path,
                Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA_TYPE),
                String.class,
                getHeaders(),
                true)
                .getRight();
    }

    private void uploadPoP() {
        MultiPart multiPart;
        StreamDataBodyPart filePart;
        multiPart = new MultiPart(MediaType.MULTIPART_FORM_DATA_TYPE);
        filePart = new StreamDataBodyPart("data",
                Objects.requireNonNull(DocumentUploaderTasks.class.getClassLoader().getResourceAsStream(INVALID_PICTURE_RESOURCE)),
                INCOME + ".jpg", MediaType.valueOf("image/jpeg"));
        multiPart.getBodyParts().add(new FormDataBodyPart("dataSize", "176.408"));
        multiPart.getBodyParts().add(filePart);
        multiPart.getBodyParts().add(new FormDataBodyPart("documentPageType", INCOME));
        sendMultipartData(UPLOAD_PATH, multiPart);
        harness.log("DocumentUploader called /upload");
    }

    private void uploadPoPFinished() {
        prepareRequest("/unsecured/document/uploader/upload-pop-finished")
                .withHeader("Channel", channelNumber)
                .withHeader("x-flow-id", xFlowID)
                .withPayload(String.format(CODE_PAYLOAD, mobileCode))
                .post()
                .andAssertStatus(200);
        harness.log("DocumentUploader called /upload-pop-finished");
    }

    private RawRestRequest.Request prepareRequest(String path) {
        return harness.withOpenApi()
                .prepareRequest()
                .withPath(path)
                .withHeader("Content-Type", "application/json")
                .withHeader("x-flow-id", xFlowID)
                .withHeader("Channel", channelNumber);
    }

    private void notify(String mobileCode) {
        xFlowID = harness.withOpenApi()
                .prepareRequest()
                .withPath("/unsecured/document/uploader/link/opened/notify")
                .withHeader("Content-Type", "application/json")
                .withHeader("Channel", channelNumber)
                .withPayload(String.format(CODE_PAYLOAD, mobileCode))
                .post()
                .andAssertStatus(200)
                .andAssertPresent("flowId")
                .extract("flowId")
                .textValue();
        harness.log(String.format("DocumentUploader called notify. xFlowID: %s", xFlowID));
        this.sleep(1);
    }

    private void takeIdentityCard(NonClient nonClient) {
        upload(ID_FRONT_RESOURCE, IDENTITY_CARD_FRONT, "185.984");
        upload(ID_BACK_RESOURCE, IDENTITY_CARD_BACK, "97.512");
        commitIdCard(nonClient);
    }

    private void commitIdCard(NonClient nonClient) {
        prepareRequest("/unsecured/document/uploader/mined-data/identity/commit")
                .withPayload(String.format(ID_COMMIT_PAYLOAD,
                        nonClient.residencyAddress().getStreet(),
                        nonClient.residencyAddress().getDescriptiveNumber(),
                        nonClient.residencyAddress().getOrientationNumber() == null ? "" : String.format(" \"houseNumber\": \"%s\",", nonClient.residencyAddress().getOrientationNumber()),
                        nonClient.residencyAddress().getCity(),
                        nonClient.residencyAddress().getZip(),
                        TestUtils.parseBirthDate(nonClient.birthNumber()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
                        nonClient.birthNumber(),
                        nonClient.placeOfBirth(),
                        nonClient.idCardCheckDigit(),
                        getDateWithFormat(nonClient.idCardExpiredRaw(), "yyyy-MM-dd"),
                        nonClient.firstName(),
                        nonClient.lastName(),
                        nonClient.idCardNumber()))
                .post()
                .andAssertStatus(200);
        harness.log("DocumentUploader called identity/commit");
        this.sleep(1);
    }

    private void uploadLivenessAndSelfie() {
        try (MultiPart multiPart = new MultiPart(MediaType.MULTIPART_FORM_DATA_TYPE)) {
            InputStream inputStream = getClass().getResourceAsStream("/regression/loans/fake_liveness.webm");
            StreamDataBodyPart filePart = new StreamDataBodyPart("file", inputStream);
            multiPart.getBodyParts().add(filePart);
            multiPart.getBodyParts().add(new FormDataBodyPart("dataSize", "224.3 KB"));
            multiPart.getBodyParts().add(new FormDataBodyPart("code", mobileCode));
            sendMultipartData(LIVENESS_PATH, multiPart);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
        upload(INVALID_PICTURE_RESOURCE, SELFIE, "176.408");
        harness.log("DocumentUploader called upload selfie");
    }

    private Map<String, Object> getHeaders() {
        Map<String, Object> headers = new HashMap<>();
        headers.put("Channel", channelNumber);
        headers.put("x-flow-id", xFlowID);
        return headers;
    }

    private void takePassport(NonClient nonClient) {
        upload(PASSPORT_RESOURCE, PASSPORT, "176.192");
        commitOtherDocument(nonClient, PASSPORT, nonClient.passportId(), nonClient.passportExpiryDateRaw());
    }

    private void takeDrivingLicence(NonClient nonClient) {
        upload(DRIVING_LICENSE_RESOURCE, DRIVING_LICENSE, "76.2");
        commitOtherDocument(nonClient, DRIVING_LICENSE, nonClient.driverLicenceId(), nonClient.driveLicenceExpiryDateRaw());
    }

    private void takeBirthCertificate(NonClient childClient, String parent1BirthNumber, String parent2BirthNumber) {
        upload(BIRTH_CERTIFICATE_RESOURCE, BIRTH_CERTIFICATE, "110.0");
        commitBirthCertificate(childClient, parent1BirthNumber, parent2BirthNumber);
    }

    private void commitOtherDocument(NonClient nonClient, String documentType, String documentID, Date documentExpirationDate) {
        prepareRequest("/unsecured/document/uploader/mined-data/other-identity/commit")
                .withPayload(String.format(OTHER_IDENTITY_PAYLOAD,
                        TestUtils.parseBirthDate(nonClient.birthNumber()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
                        nonClient.birthNumber(),
                        documentType,
                        getDateWithFormat(documentExpirationDate, "yyyy-MM-dd"),
                        nonClient.firstName(),
                        nonClient.lastName(),
                        documentID))
                .post()
                .andAssertStatus(200);
        harness.log("DocumentUploader called other-identity/commit with type: " + documentType);
        this.sleep(1);
    }

    private void commitBirthCertificate(NonClient child, String parent1BirthNumber, String parent2BirthNumber) {
        prepareRequest("/unsecured/document/uploader/mined-data/birth-certificate/commit")
                .withPayload(String.format(BIRTH_CERTIFICATE_PAYLOAD,
                        child.firstName(),
                        child.lastName(),
                        child.placeOfBirth(),
                        TestUtils.parseBirthDate(child.birthNumber()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
                        child.birthNumber(),
                        parent1BirthNumber,
                        parent2BirthNumber))
                .post()
                .andAssertStatus(200);
        harness.log("DocumentUploader called birth-certificate/commit");
        this.sleep(1);
    }

    private void sleep(int seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException var5) {
            logger.warn("Sleep throws InterruptedException!", var5);
            Thread.currentThread().interrupt();
        }
    }
}
