package com.capgemini.reports.common;

import com.capgemini.framework.common.MailHelper;
import com.capgemini.framework.common.SettingsHelper;
import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.exceptions.KurtException;
import com.capgemini.framework.teamcenter.exceptions.NoTeamcenterConnectionException;
import com.capgemini.framework.teamcenter.policies.PolicyLocation;
import com.capgemini.reports.ReportEnum;
import com.capgemini.reports.ReportLaunchTypeEnum;
import com.capgemini.reports.builder.ReportBuilder;
import com.capgemini.reports.database.javabean.enumeration.ReportCriteriaEnum;
import com.capgemini.reports.database.manager.MngCriteriaVal.CriteriaValue;
import com.capgemini.reports.exceptions.InvalidInputException;
import com.capgemini.reports.exceptions.InvalidUserInputException;
import com.capgemini.reports.exceptions.UserInputErrorEnum;
import com.capgemini.reports.execution.AbstractExecutionReport;
import com.capgemini.reports.execution.IDataReportPreparator;
import com.capgemini.reports.execution.mapconstructor.AbstractStaticTemplateMapConstructor;
import com.capgemini.reports.execution.mapconstructor.IMapConstructor;
import com.capgemini.reports.execution.strategies.IExecutionStrategy;
import com.capgemini.reports.printing.PrintParameters;
import com.capgemini.reports.printing.Printer.PrintFormat;
import com.capgemini.reports.queuing.ReportPriority;
import com.capgemini.reports.reports.parameters.IChecker;
import com.teamcenter.schemas.soa._2006_03.exceptions.ServiceException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.lingala.zip4j.exception.ZipException;
import org.json.JSONObject;
import org.junit.Assert;
import org.junit.Test;
import org.quartz.SchedulerException;
import org.springframework.mock.web.MockHttpServletRequest;

import javax.jms.JMSException;
import javax.naming.NamingException;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;

/** JUnit test. **/
@Slf4j
public class AbstractSubmitServletTest {

    private static final String FAKE_REPORT = "FAKE_REPORT";
    private static final ReportPriority EXPECTED_REPORT_PRIORITY = ReportPriority.HIGH_PRIORITY;
    private static final PolicyLocation XML_POLICY = PolicyLocation.make("test_policy", "/xml/policies/test_policy.xml");
    /** A non-space string of chars, followed by '@', followed by a non-space string of chars, followed by a dot ('.'), followed by a non-space string of chars. */
    private static final Pattern EMAIL_ADDRESS_REGEX = Pattern.compile("\\S+@\\S+\\.\\S+");

    /**
     * JUnit test. All tests must pass, the report should be submitted and an email sent to the user.
     *
     * @throws InvalidInputException Should not happen
     * @throws JMSException Should not happen
     * @throws ServiceException Should not happen
     * @throws NamingException Should not happen
     * @throws SchedulerException Should not happen
     **/
    @Test
    public void testPerformSuccess() throws InvalidInputException, JMSException, ServiceException, NamingException, SchedulerException, NoTeamcenterConnectionException {
        // Create Servlet instance
        log.info("Create Servlet instance");
        SomeGoodParameter quickCheck = new SomeGoodParameter();
        SomeGoodParameter longCheck = new SomeGoodParameter();
        SomeExecutionReport goodReport = new SomeExecutionReport(quickCheck, longCheck, new SomeDataPreparator(), new SomeMapConstructor());
        SomeSubmitServlet testerServlet = new SomeSubmitServlet(goodReport);

        // Execute servlet POST operation
        log.info("Execute Good servlet POST operation");
        JSONObject json = testPerform(testerServlet);

        // Verify Report TcReleaseStatus : posted, start email sent, but not executed
        log.info("Verify Report TcReleaseStatus : params checked, report posted, start email sent, but not executed");
        Assert.assertTrue(quickCheck.isChecked);
        // TODO (Long Checks not yet activated) Assert.assertTrue(longCheck.isChecked);
        Assert.assertNotNull(testerServlet.theReport);
        Assert.assertFalse(testerServlet.theReport.isParametersCheckFailureEmailDone);
        Assert.assertTrue(testerServlet.theReport.isStartEmailDone);
        Assert.assertFalse(testerServlet.theReport.isPrepareDataDone());
        Assert.assertFalse(testerServlet.theReport.isEndEmailDone);
        Assert.assertFalse(testerServlet.theReport.isErrorEmailDone);
        Assert.assertEquals(EXPECTED_REPORT_PRIORITY, goodReport.getPriorityCode());

        // Verify JSON (should be success)
        log.info("Verify HttpServletResponse to the server (should be success)");
        log.info("raw JSON : " + json);
        Assert.assertEquals(ServletMessageHelper.STATUS_SUCCESS, json.get(ServletMessageHelper.TOKEN_STATUS));
        log.info(json.get(ServletMessageHelper.STATUS_SUCCESS).toString());
    }

    /**
     * JUnit test. Report check must fail. Report should not be submitted, an explanation should be sent to the user.
     *
     * @throws JMSException Should not happen
     * @throws ServiceException Should not happen
     * @throws NamingException Should not happen
     * @throws SchedulerException Should not happen
     **/
    @Test
    public void testPerformServletEarlyFail() throws JMSException, ServiceException, NamingException, SchedulerException, NoTeamcenterConnectionException {
        // Create Servlet instance
        log.info("Create Servlet instance");
        SomeFailingParameter quickCheck = new SomeFailingParameter();
        SomeGoodParameter longCheck = new SomeGoodParameter();
        SomeExecutionReport badReport = new SomeExecutionReport(quickCheck, longCheck, new SomeDataPreparator(), new SomeMapConstructor());
        SomeSubmitServlet testerServlet = new SomeSubmitServlet(badReport);

        // Execute servlet POST operation
        log.info("Execute Bad servlet POST operation");
        try {
            testPerform(testerServlet);
        } catch (InvalidInputException e) {
            // Verify Report TcReleaseStatus : NOT posted, no eMail sent, report not executed
            log.info("Verify Report TcReleaseStatus : NOT posted, no eMail sent, report not executed");
            Assert.assertNotNull(testerServlet.theReport);
            Assert.assertFalse(testerServlet.theReport.isParametersCheckFailureEmailDone);
            Assert.assertFalse(testerServlet.theReport.isStartEmailDone);
            Assert.assertFalse(testerServlet.theReport.isPrepareDataDone());
            Assert.assertFalse(testerServlet.theReport.isEndEmailDone);
            log.info("Test successful", e);
            return;
        }
        log.info("Test failed!");
        Assert.fail();
    }

    /**
     * JUnit test. Report check must fail. Report should not be submitted, an explanation should be sent to the user.
     *
     * @throws JMSException Should not happen
     * @throws InvalidInputException Should not happen
     **/
    @Test
    public void testPerformServletLateFail() throws JMSException, InvalidInputException {
        /*
        // Create Servlet instance
        log.info("Create Servlet instance");
        SomeGoodParameter quickCheck = new SomeGoodParameter();
        SomeFailingParameter longCheck = new SomeFailingParameter();
        SomeExecutionReport badReport = new SomeExecutionReport(quickCheck, longCheck);
        SomeSubmitServlet testerServlet = new SomeSubmitServlet(badReport);
        SomeMessagingServer messagingServer = new SomeMessagingServer();
        
        // Execute servlet POST operation
        log.info("Execute Good servlet POST operation");
        JSONObject json = testPerform(testerServlet, messagingServer);
        
        // Verify Report TcReleaseStatus : NOT posted, error eMail sent, report not executed
        log.info("Verify Report TcReleaseStatus : NOT posted, Error eMail sent, report not executed");
        Assert.assertFalse(messagingServer.reportPosted);
        Assert.assertNotNull(testerServlet.theReport);
        Assert.assertTrue(quickCheck.isChecked);
        Assert.assertTrue(testerServlet.theReport.isParametersCheckFailureEmailDone);
        Assert.assertFalse(testerServlet.theReport.isStartEmailDone);
        Assert.assertFalse(testerServlet.theReport.isPrepareDataDone);
        Assert.assertFalse(testerServlet.theReport.isConstructDataMapsDone);
        Assert.assertFalse(testerServlet.theReport.isEndEmailDone);
        
        // Verify HttpServletResponse (should be failed)
        log.info("Verify HttpServletResponse to the server (should be failed, internal error)");
        Assert.assertEquals(AbstractSubmitServlet.SUCCESS, json.findAll(AbstractSubmitServlet.STATUS));
        Assert.assertEquals("BOM assemblage PU successfully posted in the queues", json.findAll(AbstractSubmitServlet.SUCCESS));
        log.info("Test successful");*/
    }

    private JSONObject testPerform(final AbstractSubmitServlet<SomeExecutionReport> servlet)
            throws InvalidInputException, JMSException, ServiceException, NamingException, SchedulerException, NoTeamcenterConnectionException {
        // Setup request and responses
        log.info("testPerform(): Setup request and responses");
        HttpServletRequest request = new MockHttpServletRequest();

        // Execute servlet POST operation
        log.info("Execute Good servlet POST operation");
        try (final TcSession session = new FakeTcSession()) {
            // Return the servlet response for further checks
            return servlet.perform(request, session);
        }
    }


  @Test
    public void getDefaultEmailDestinatorsTest() {
        log.info("Input: '" + SettingsHelper.getDefaultDestinatorEmail());
        List<String> defaults = MailHelper.DEFAULT_DESTINATORS;
        Assert.assertFalse(defaults.isEmpty());
        for (String user : defaults) {
            log.info("'" + user + "'");
            Assert.assertTrue(EMAIL_ADDRESS_REGEX.matcher(user).matches());
        }
    }

    /** A placeholder implementation of SubmitServlet for testing. */
    @Getter
    private class SomeSubmitServlet extends AbstractSubmitServlet<SomeExecutionReport> {
        private static final long serialVersionUID = 8030290777260376309L;
        private final SomeExecutionReport theReport;

        public SomeSubmitServlet(final SomeExecutionReport report) {
            this.theReport = report;
        }

        @Override
        public SomeExecutionReport prepareReport(final HttpServletRequest request) {
            return theReport;
        }
    }

    @Getter
    private class SomeDataPreparator implements IDataReportPreparator<SomeExecutionReport> {
        private static final long serialVersionUID = 4832031208271989060L;
        private boolean done;

        @Override
        public String print(TcSession session, SomeExecutionReport execution, IExecutionStrategy executionStrategy) throws KurtException, ZipException {
            done = true;
            return null;
        }
    }

    /**
     * Fake {@link TcSession} that does nothing.
     */
    private class FakeTcSession extends TcSession {

        public FakeTcSession() {
            super();
        }

        @Override
        public void close() {
            // Do not logout!
        }

    }

    @Getter
    private class SomeMapConstructor extends AbstractStaticTemplateMapConstructor<SomeExecutionReport, Void> {
        private static final long serialVersionUID = -6387054969518914371L;
        private boolean done;

        @Override
        public void constructDataMaps(TcSession session, final PrintParameters printParameters, final Void report) throws KurtException {
            done = true;
        }

    }

    /** A placeholder implementation of an ExecutionReport. */
    @Getter
    private class SomeExecutionReport extends AbstractExecutionReport<SomeExecutionReport, Void> {

        private static final long serialVersionUID = 3104147701132381151L;

        private boolean isStartEmailDone;
        private boolean isEndEmailDone;
        private boolean isErrorEmailDone;
        private boolean isParametersCheckFailureEmailDone;

        final List<IChecker> quickChecks;
        final List<IChecker> longChecks;

        public SomeExecutionReport(final IChecker quickCheck, final IChecker longCheck,
                                   final IDataReportPreparator<SomeExecutionReport> prepaData, final IMapConstructor<SomeExecutionReport, Void> constructMap) {
            super(EXPECTED_REPORT_PRIORITY.getPriorityValue(), "someRule", ReportLaunchTypeEnum.IMMEDIATE);// the highest priority code, launch type is immediate
            quickChecks = new ArrayList<>();
            quickChecks.add(quickCheck);
            longChecks = new ArrayList<>();
            longChecks.add(longCheck);
        }

        public boolean isPrepareDataDone() {
            return ((SomeDataPreparator) this.getDataPreparator()).isDone();
        }

        @Override
        public ReportPriority getReportPriority() {
            isStartEmailDone = true;
            return EXPECTED_REPORT_PRIORITY;
        }


        @Override
        public String getSpecifiedFileName() {
            return FAKE_REPORT;
        }

        @Override
        public PrintFormat getPrintingFormat() {
            return PrintFormat.XLSX;
        }

        @Override
        public List<IChecker> getQuickChecks() {
            return quickChecks;
        }

        @Override
        public List<IChecker> getLongChecks() {
            return longChecks;
        }

        @Override
        public ReportEnum getReportIdentifier() {
            return ReportEnum.REPORT_R01;
        }

        @Override
        public PolicyLocation getPropertyPolicyForLongChecks() {
            return XML_POLICY;
        }

        @Override
        public PolicyLocation getFullPolicy() {
            return XML_POLICY;
        }

        @Override
        public String getTemplateLocation() {
            return "somewhere";
        }

        @Override
        public List<CriteriaValue> getLaunchData() {
            List<CriteriaValue> crits = new ArrayList<>();
            crits.add(new CriteriaValue(ReportCriteriaEnum.PRODUCT, UUID.randomUUID().toString()));
            return crits; // Random Data to avoid duplicates
        }

        @Override
        public IDataReportPreparator<SomeExecutionReport> getDataPreparator() {
            return new SomeDataPreparator();
        }

        @Override
        public AbstractExecutionReport<?, ?> cloneTemplateToExecution() {
            SomeFailingParameter quickCheck = new SomeFailingParameter();
            SomeGoodParameter longCheck = new SomeGoodParameter();
            return new SomeExecutionReport(quickCheck, longCheck, new SomeDataPreparator(), new SomeMapConstructor());
        }

        @Override
        public Class<? extends ReportBuilder> getReportBuilderClass() {
            return null;
        }

    }

    private class SomeGoodParameter implements IChecker {
        boolean isChecked;

        @Override
        public void check(final TcSession session) throws InvalidUserInputException {
            isChecked = true;
        }
    }

    private class SomeFailingParameter implements IChecker {

        @Override
        public void check(final TcSession session) throws InvalidUserInputException {
            throw new InvalidUserInputException(UserInputErrorEnum.INTERNAL_ERROR);
        }
    }
}
