package com.workfusion.odf.impl.converter.html2pdf;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;

import com.freedomoss.crowdcontrol.webharvest.CampaignDto;
import com.freedomoss.crowdcontrol.webharvest.ProxySettingsDto;
import com.freedomoss.crowdcontrol.webharvest.SourceDto;
import com.freedomoss.crowdcontrol.webharvest.WebHarvestTaskItem;
import com.freedomoss.crowdcontrol.webharvest.plugin.selenium.DriverInfo;
import com.freedomoss.crowdcontrol.webharvest.selenium.wrapper.RemoteDriverWrapper;
import com.freedomoss.crowdcontrol.webharvest.util.ProxyInfo;
import groovy.lang.Binding;
import mockit.Delegate;
import mockit.Expectations;
import mockit.Invocation;
import mockit.Mock;
import mockit.MockUp;
import mockit.Mocked;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.codejargon.feather.Provides;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.remote.BrowserType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.slf4j.Logger;
import org.springframework.http.MediaType;
import org.webharvest.definition.ScraperConfiguration;
import org.webharvest.runtime.Scraper;
import org.webharvest.runtime.ScraperContext;
import org.webharvest.utils.SystemUtilities;
import org.xml.sax.InputSource;

import com.workfusion.odf.core.Intake;
import com.workfusion.odf.core.Module;
import com.workfusion.rpa.core.BindingUtils;
import com.workfusion.rpa.core.storage.S3Manager;
import com.workfusion.rpa.helpers.RPA;
import com.workfusion.rpa.helpers.utils.ApiUtils;

import static com.workfusion.odf.impl.converter.html2pdf.RpaPdfReportGenerator.CHECK_THAT_FILE_EXISTS_SCRIPT;
import static com.workfusion.odf.impl.converter.html2pdf.RpaPdfReportGenerator.COMPLETION_SUCCESS_MESSAGE;
import static com.workfusion.odf.impl.converter.html2pdf.RpaPdfReportGenerator.EMPTY_PDF_FILE_GENERATION_SCRIPT;
import static com.workfusion.odf.impl.converter.html2pdf.RpaPdfReportGenerator.GENERATE_PDF_SCRIPT_TEMPLATE;
import static com.workfusion.odf.impl.converter.html2pdf.RpaPdfReportGenerator.S3_BUCKET_KEY;
import static com.workfusion.odf.impl.converter.html2pdf.RpaPdfReportGenerator.S3_FOLDER_KEY;

public class RpaPdfReportGeneratorTest {

    @Inject
    private RpaPdfReportGenerator rpaReportGenerator;

    @Mocked
    private Logger logger;
    @Mocked
    private Binding binding;

    @Mocked
    private S3Manager s3Manager;

    private Intake intake;
    private final String INPUT_WEB_LINK = "https://www.tut.by/";
    private final static String PDF_PATH = "/html2pdf/aerbret.pdf";

    @Mocked
    private DriverInfo driverInfo;

    private ProxyInfo proxyInfo = new ProxyInfo("asdfbv", 5678);
    @Mocked
    private RemoteDriverWrapper desktopDriver;

    private SystemUtilities sys;
    private WebHarvestTaskItem item;
    private ScraperContext context;
    private final static byte[] pdf;

    static {
        try {
            pdf = IOUtils.resourceToByteArray(PDF_PATH);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @BeforeEach
    public void init() throws Exception {
        mockDriver();

        mockContext();

        cleanProxyContext();
        cleanS3Context();

        Module testModule = new Module() {
            @Provides
            public S3Manager s3Manager() {
                return s3Manager;
            }

            @Provides
            public Logger logger() {
                return logger;
            }

            @Provides
            public Binding getBinding() {
                return binding;
            }
        };

        intake = Intake.init(binding).override(testModule).injectFields(this).get();
    }

    @Test
    public void generatePdf() {
        final byte[] generatedPdf = rpaReportGenerator.generatePdf(INPUT_WEB_LINK);
        Assertions.assertArrayEquals(generatedPdf, generatedPdf);
    }

    @Test
    void generatePdfAndUploadToS3() {
        new MockUp<S3Manager>() {
            @Mock
            public String uploadFileToS3(Invocation invocation, final byte[] newDocumentToUpload, final String fileName, String contentType) {
                if (Arrays.equals(newDocumentToUpload, pdf)) {
                    return fileName;
                } else {
                    return invocation.proceed(newDocumentToUpload, fileName, contentType);
                }
            }
        };

        final String defaultLink = rpaReportGenerator.generatePdfAndUploadToS3(INPUT_WEB_LINK);
        Assertions.assertTrue(defaultLink.matches(".*?html2pdf\\/(\\w|-)*\\.pdf"));

    }

    @Test
    void generatePdfAndUploadToS3CustomS3() {
        final String customS3bucket = "s3bucket";

        new MockUp<S3Manager>() {
            @Mock
            public String uploadFileToS3(Invocation invocation, String bucket, final byte[] newDocumentToUpload, final String fileName, String contentType) {
                if (bucket.equals(customS3bucket) && Arrays.equals(newDocumentToUpload,
                        pdf) && contentType.equals(MediaType.APPLICATION_PDF_VALUE)) {
                    return bucket + "/" + fileName;
                } else {
                    return invocation.proceed(bucket, newDocumentToUpload, fileName, contentType);
                }
            }
        };

        final String s3PathForOutputFile = "folder1/folder2";
        final String customisedLink = rpaReportGenerator.generatePdfAndUploadToS3(INPUT_WEB_LINK, customS3bucket, s3PathForOutputFile);
        final String regexFrorExpectedURL = ".*?" + s3PathForOutputFile + "\\/(\\w|-)*\\.pdf";
        Assertions.assertTrue(customisedLink.matches(regexFrorExpectedURL));

        sys.defineVariable(S3_BUCKET_KEY, customS3bucket);
        sys.defineVariable(S3_FOLDER_KEY, s3PathForOutputFile);

        final String customisedLink2 = rpaReportGenerator.generatePdfAndUploadToS3(INPUT_WEB_LINK);
        Assertions.assertTrue(customisedLink2.matches(regexFrorExpectedURL));
    }

    @Test
    public void cnstructorS3ParamsNegativeTest() {
        cleanProxyContext();
        cleanS3Context();

        cleanS3Context();
        sys.defineVariable(S3_BUCKET_KEY, "");
        sys.defineVariable(S3_FOLDER_KEY, "ghgj");
        Assertions.assertThrows(IllegalArgumentException.class, () -> new RpaPdfReportGenerator(s3Manager, logger, binding));

        cleanS3Context();
        sys.defineVariable(S3_BUCKET_KEY, null);
        sys.defineVariable(S3_FOLDER_KEY, "ghgj");
        Assertions.assertThrows(IllegalArgumentException.class, () -> new RpaPdfReportGenerator(s3Manager, logger, binding));
    }

    @Test
    public void constructorS3ParamsPositiveTest() {
        cleanProxyContext();
        cleanS3Context();

        sys.defineVariable(S3_BUCKET_KEY, null);
        Assertions.assertDoesNotThrow(() -> new RpaPdfReportGenerator(s3Manager, logger, binding));

        cleanS3Context();
        sys.defineVariable(S3_BUCKET_KEY, "");
        Assertions.assertDoesNotThrow(() -> new RpaPdfReportGenerator(s3Manager, logger, binding));

        cleanS3Context();
        sys.defineVariable(S3_BUCKET_KEY, "jkg");
        sys.defineVariable(S3_FOLDER_KEY, "");
        Assertions.assertDoesNotThrow(() -> new RpaPdfReportGenerator(s3Manager, logger, binding));

        cleanS3Context();
        sys.defineVariable(S3_BUCKET_KEY, "jkg");
        sys.defineVariable(S3_FOLDER_KEY, "kykguf");
        Assertions.assertDoesNotThrow(() -> new RpaPdfReportGenerator(s3Manager, logger, binding));
    }

    @Test
    public void testConstructorWithProxyPositiveTest() {
        cleanProxyContext();
        Assertions.assertDoesNotThrow(() -> new RpaPdfReportGenerator(s3Manager, logger, binding));

        ProxySettingsDto proxySettingsDto = new ProxySettingsDto();
        item.getCampaignDto().getSource().setProxySettings(proxySettingsDto);

        List<Pair<String, Integer>> successfullProxyParams = new ArrayList<>();
        successfullProxyParams.add(Pair.of(null, null));
        successfullProxyParams.add(Pair.of("sgf", 9));

        for (Pair<String, Integer> successfullProxyParam : successfullProxyParams) {
            proxySettingsDto.setServer(successfullProxyParam.getKey());
            proxySettingsDto.setPort(successfullProxyParam.getValue());
            Assertions.assertDoesNotThrow(() -> new RpaPdfReportGenerator(s3Manager, logger, binding));
        }
    }

    private void mockContext() {
        final ScraperConfiguration scraperConfiguration = new ScraperConfiguration((new InputSource(new StringReader(
                "<fake_bot_config scriptlang='groovy'/>"))));
        final Scraper scraper = new Scraper(scraperConfiguration, "");
        context = scraper.getContext();
        sys = new SystemUtilities(scraper);

        this.item = new WebHarvestTaskItem();
        CampaignDto campaignDto = new CampaignDto();
        SourceDto sourceDto = new SourceDto();
        campaignDto.setSource(sourceDto);
        item.setCampaignDto(campaignDto);
        sys.defineVariable("item", item);

        new MockUp<BindingUtils>() {
            @Mock
            public SystemUtilities getSys(Invocation invocation, final Binding binding) {
                return sys;
            }
        };
    }

    private void mockDriver() throws Exception {

        ApiUtils.setDriver(desktopDriver);
        final DesiredCapabilities value = new DesiredCapabilities();
        value.setCapability("browserName", "desktop");
        FieldUtils.writeField(desktopDriver, "capabilities", value, true);

        final String filePath = "../external_libs/" + RpaPdfReportGenerator.HTML_2_PDF_PRINTER_JAR_NAME;

        final String generatePdfScript = String.format(GENERATE_PDF_SCRIPT_TEMPLATE, filePath, INPUT_WEB_LINK, PDF_PATH);

        final String expectedCommandRegex = GENERATE_PDF_SCRIPT_TEMPLATE
                .replaceFirst("%s", filePath)
                .replaceFirst("%s", ".+?")
                .replace("(", "\\(")
                .replace(")", "\\)");

        new MockUp<DriverInfo>() {
            @Mock
            public String getType() {
                return BrowserType.DESKTOP;
            }
        };
        new Expectations(desktopDriver) {{
            desktopDriver.getDriverInfo();
            result = driverInfo;
            minTimes = 0;
            desktopDriver.executeScript(withEqual(EMPTY_PDF_FILE_GENERATION_SCRIPT), withEqual("GROOVY"), any);
            result = PDF_PATH;
            minTimes = 0;
            desktopDriver.executeScript(withEqual(generatePdfScript), withEqual("GROOVY"), withInstanceOf(Map.class));
            result = PDF_PATH;
            minTimes = 0;
            desktopDriver.executeScript(withMatch(expectedCommandRegex), withEqual("GROOVY"));
            minTimes = 0;
            result = new Delegate() {
                public Object aDelegateMethod(String script, Object... args) {
                    return "zzz" + COMPLETION_SUCCESS_MESSAGE + "xxc";
                }
            };
            desktopDriver.executeScript(withEqual(CHECK_THAT_FILE_EXISTS_SCRIPT), withEqual("GROOVY"), withInstanceOf(Map.class));
            result = true;
            minTimes = 0;

        }};

        new MockUp<RPA>() {
            @Mock
            public byte[] downloadFileFromAgent(String value) {
                try {
                    return IOUtils.resourceToByteArray(PDF_PATH);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

            @Mock
            public boolean deleteFileOnAgent(String value) {
                return true;
            }
        };
    }

    private void cleanProxyContext() {
        item.getCampaignDto().getSource().setProxySettings(null);
    }

    private void cleanS3Context() {
        context.remove(S3_BUCKET_KEY);
        context.remove(S3_FOLDER_KEY);
    }

}
