package com.workfusion.odf.impl.connector;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.inject.Named;

import org.codejargon.feather.Provides;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.workfusion.odf.api.domain.Transaction;
import com.workfusion.odf.core.Intake;
import com.workfusion.odf.core.Module;
import com.workfusion.rpa.core.storage.S3Manager;

import groovy.lang.Binding;
import jcifs.smb.SmbFile;
import jcifs.smb.SmbFilenameFilter;

/**
 * Share Folder (SMB) connector unit tests.
 *
 * @author Andrei Urbanovich
 */
@DisplayName("ShareFolder (SMB) connector unit tests")
public class ShareFolderTransactionSupplierTest {
	private static final String TEST_SMB_ALIAS = "smbuser";
	private static final String TEST_SMB_FILTER = "*";
	private static final String TEST_SMB_HOST = "localhost";

	private static final String TEST_FILE_1 = "presentation.pdf";
	private static final String TEST_FILE_1_PROCESSED = "presentation" + ShareFolderTransactionSupplier.MARK_AS_READ_POSTFIX + ".pdf";
	private static final String TEST_FILE_1_PATH = ShareFolderTransactionSupplier.SMB_PROTOCOL + TEST_SMB_HOST + "/" + TEST_FILE_1;
	private static final String TEST_FILE_1_PATH_PROCESSED = ShareFolderTransactionSupplier.SMB_PROTOCOL + TEST_SMB_HOST + "/" + TEST_FILE_1_PROCESSED;
	private static final String TEST_FILE_2 = "TODO.txt";
	private static final String TEST_FILE_2_PROCESSED = "TODO" + ShareFolderTransactionSupplier.MARK_AS_READ_POSTFIX + ".txt";
	private static final String TEST_FILE_2_PATH = ShareFolderTransactionSupplier.SMB_PROTOCOL + TEST_SMB_HOST + "/" + TEST_FILE_2;
	private static final String TEST_FILE_2_PATH_PROCESSED = ShareFolderTransactionSupplier.SMB_PROTOCOL + TEST_SMB_HOST + "/" + TEST_FILE_2_PROCESSED;
	private static final String TEST_FOLDER_1 = "folder1";
	private static final String TEST_FOLDER_1_PROCESSED = TEST_FOLDER_1 + ShareFolderTransactionSupplier.MARK_AS_READ_POSTFIX;
	private static final String TEST_FOLDER_1_PATH = ShareFolderTransactionSupplier.SMB_PROTOCOL + TEST_SMB_HOST + "/" + TEST_FOLDER_1;
	private static final String TEST_FOLDER_1_PATH_PROCESSED = ShareFolderTransactionSupplier.SMB_PROTOCOL + TEST_SMB_HOST + "/" + TEST_FOLDER_1_PROCESSED;
	private static final String TEST_FOLDER_1_FILE_1 = "scan1.png";
	private static final String TEST_FOLDER_1_FILE_1_PROCESSED = "scan1" + ShareFolderTransactionSupplier.MARK_AS_READ_POSTFIX + ".png";
	private static final String TEST_FOLDER_1_FILE_1_PATH = ShareFolderTransactionSupplier.SMB_PROTOCOL + TEST_SMB_HOST + "/" + TEST_FOLDER_1 + "/" + TEST_FOLDER_1_FILE_1;
	private static final String TEST_FOLDER_1_FILE_1_PATH_PROCESSED = ShareFolderTransactionSupplier.SMB_PROTOCOL + TEST_SMB_HOST + "/" + TEST_FOLDER_1 + "/" + TEST_FOLDER_1_FILE_1_PROCESSED;


	@Inject
    private ShareFolderTransactionSupplier connector;

    private SmbFileBuilder smbFileBuilder;

    private S3Manager s3Manager;

    private Logger logger;

    private Binding binding;

    private Map<String, String> params;

    @BeforeEach
    public void init() {
        s3Manager = Mockito.mock(S3Manager.class);
        logger = LoggerFactory.getLogger(ShareFolderTransactionSupplierTest.class);
        smbFileBuilder = Mockito.mock(SmbFileBuilder.class);
        binding = Mockito.mock(Binding.class);
        params = Mockito.mock(Map.class);

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

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

            @Provides
            public SmbFileBuilder smbFileBuilder() {
                return smbFileBuilder;
            }

            @Provides
            @Named("botConfigParams")
            public Map<String, String> params() {
                return params;
            }
        };

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

	@Test
    public void testGetTransactions() throws Exception {
	    SmbFile smbFile1Mock = mockFile1();
	    SmbFile smbFile2Mock = mockFile2();
	    SmbFile smbFile3Mock = mockFile3();
	    SmbFile smbFolder1Mock = mockFolder1(smbFile3Mock);
		SmbFile smbHomeDirMock = mockSmbHomeDir(smbFile1Mock, smbFile2Mock, smbFolder1Mock);

        Mockito.when(params.get(ShareFolderTransactionSupplier.PARAM_HOST)).thenReturn(TEST_SMB_HOST);
        Mockito.when(params.get(ShareFolderTransactionSupplier.PARAM_PATH)).thenReturn("/");
        Mockito.when(params.get(ShareFolderTransactionSupplier.PARAM_ALIAS)).thenReturn(TEST_SMB_ALIAS);
        Mockito.when(params.get(ShareFolderTransactionSupplier.PARAM_WILDCARD)).thenReturn(TEST_SMB_FILTER);

        Mockito.when(smbFileBuilder.buildSmbFileByPath(ArgumentMatchers.eq(ShareFolderTransactionSupplier.SMB_PROTOCOL + TEST_SMB_HOST + "/"))).thenReturn(smbHomeDirMock);

    	// 's3Manager' field mock
    	Mockito.when(s3Manager.putFile(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn("s3-file.ext");

    	List<Transaction> transactions = new ArrayList<>(connector.get());

    	Assertions.assertNotNull(transactions);
    	Assertions.assertTrue(transactions.size() == 3);

    	Transaction transaction1 = transactions.get(0);
    	Assertions.assertNotNull(transaction1.getDocs());
    	Assertions.assertTrue(transaction1.getDocs().size() == 1);

    	Transaction transaction2 = transactions.get(1);
    	Assertions.assertNotNull(transaction2.getDocs());
    	Assertions.assertTrue(transaction2.getDocs().size() == 1);

    	Transaction transaction3 = transactions.get(2);
    	Assertions.assertNotNull(transaction3.getDocs());
    	Assertions.assertTrue(transaction3.getDocs().size() == 1);
	}

	@Test
    public void testRemoveOnRead() throws Exception {
	    SmbFile smbFile1Mock = mockFile1();
	    SmbFile smbFile2Mock = mockFile2();
	    SmbFile smbFile3Mock = mockFile3();
	    SmbFile smbFolder1Mock = mockFolder1(smbFile3Mock);
		SmbFile smbHomeDirMock = mockSmbHomeDir(smbFile1Mock, smbFile2Mock, smbFolder1Mock);

        Mockito.when(params.get(ShareFolderTransactionSupplier.PARAM_HOST)).thenReturn(TEST_SMB_HOST);;
        Mockito.when(params.get(ShareFolderTransactionSupplier.PARAM_HOST)).thenReturn(TEST_SMB_HOST);
        Mockito.when(params.get(ShareFolderTransactionSupplier.PARAM_PATH)).thenReturn("/");
        Mockito.when(params.get(ShareFolderTransactionSupplier.PARAM_ALIAS)).thenReturn(TEST_SMB_ALIAS);
        Mockito.when(params.get(ShareFolderTransactionSupplier.PARAM_WILDCARD)).thenReturn(TEST_SMB_FILTER);
        Mockito.when(params.get(ShareFolderTransactionSupplier.PARAM_ACTION_ON_READ)).thenReturn(ShareFolderTransactionSupplier.PARAM_ACTION_ON_READ_DELETE_ON_READ);

        Mockito.when(smbFileBuilder.buildSmbFileByPath(ArgumentMatchers.eq(ShareFolderTransactionSupplier.SMB_PROTOCOL + TEST_SMB_HOST + "/"))).thenReturn(smbHomeDirMock);

    	// 's3Manager' field mock
    	Mockito.when(s3Manager.putFile(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn("s3-file.ext");

    	List<Transaction> transactions = new ArrayList<>(connector.get());

    	Assertions.assertNotNull(transactions);
    	Assertions.assertTrue(transactions.size() == 3);

    	// Verify 'Remove on read' feature
        Mockito.verify(smbFile1Mock, Mockito.times(1)).delete();
        Mockito.verify(smbFile2Mock, Mockito.times(1)).delete();
        Mockito.verify(smbFile3Mock, Mockito.times(1)).delete();
        Mockito.verify(smbFolder1Mock, Mockito.times(1)).delete();
	}

	@Test
    public void testMarkAsRead() throws Exception {
	    SmbFile smbFile1Mock = mockFile1();
	    SmbFile smbFile2Mock = mockFile2();
	    SmbFile smbFile3Mock = mockFile3();
	    SmbFile smbFolder1Mock = mockFolder1(smbFile3Mock);
		SmbFile smbHomeDirMock = mockSmbHomeDir(smbFile1Mock, smbFile2Mock, smbFolder1Mock);

        Mockito.when(params.get(ShareFolderTransactionSupplier.PARAM_HOST)).thenReturn(TEST_SMB_HOST);;
        Mockito.when(params.get(ShareFolderTransactionSupplier.PARAM_HOST)).thenReturn(TEST_SMB_HOST);
        Mockito.when(params.get(ShareFolderTransactionSupplier.PARAM_PATH)).thenReturn("/");
        Mockito.when(params.get(ShareFolderTransactionSupplier.PARAM_ALIAS)).thenReturn(TEST_SMB_ALIAS);
        Mockito.when(params.get(ShareFolderTransactionSupplier.PARAM_WILDCARD)).thenReturn(TEST_SMB_FILTER);
        Mockito.when(params.get(ShareFolderTransactionSupplier.PARAM_ACTION_ON_READ)).thenReturn(ShareFolderTransactionSupplier.PARAM_ACTION_ON_READ_MARK_AS_READ);

        Mockito.when(smbFileBuilder.buildSmbFileByPath(ArgumentMatchers.eq(ShareFolderTransactionSupplier.SMB_PROTOCOL + TEST_SMB_HOST + "/"))).thenReturn(smbHomeDirMock);
        
        SmbFile smbRenameToFileMock = Mockito.mock(SmbFile.class);
        Mockito.when(smbFileBuilder.buildSmbFileByPath(ArgumentMatchers.eq(TEST_FILE_1_PATH_PROCESSED))).thenReturn(smbRenameToFileMock);
        Mockito.when(smbFileBuilder.buildSmbFileByPath(ArgumentMatchers.eq(TEST_FILE_2_PATH_PROCESSED))).thenReturn(smbRenameToFileMock);
        Mockito.when(smbFileBuilder.buildSmbFileByPath(ArgumentMatchers.eq(TEST_FOLDER_1_PATH_PROCESSED))).thenReturn(smbRenameToFileMock);
        Mockito.when(smbFileBuilder.buildSmbFileByPath(ArgumentMatchers.eq(TEST_FOLDER_1_FILE_1_PATH_PROCESSED))).thenReturn(smbRenameToFileMock);

    	// 's3Manager' field mock
    	Mockito.when(s3Manager.putFile(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn("s3-file.ext");

    	List<Transaction> transactions = new ArrayList<>(connector.get());

    	Assertions.assertNotNull(transactions);
    	Assertions.assertTrue(transactions.size() == 3);

    	// Verify 'Remove on read' feature
        Mockito.verify(smbFile1Mock, Mockito.times(1)).renameTo(ArgumentMatchers.any());
        Mockito.verify(smbFile2Mock, Mockito.times(1)).renameTo(ArgumentMatchers.any());
        Mockito.verify(smbFile3Mock, Mockito.times(1)).renameTo(ArgumentMatchers.any());
	}

	/**
	 * Mock for SMB file #1 (1-st level file).
	 *
	 * @throws IOException
	 */
    private SmbFile mockFile1() throws IOException {
        SmbFile smbFile1Mock = Mockito.mock(SmbFile.class);
        Mockito.when(smbFile1Mock.isDirectory()).thenReturn(Boolean.FALSE);
        Mockito.when(smbFile1Mock.isFile()).thenReturn(Boolean.TRUE);
        Mockito.when(smbFile1Mock.getInputStream()).thenReturn(null);
        Mockito.when(smbFile1Mock.getName()).thenReturn(TEST_FILE_1);
        Mockito.when(smbFile1Mock.getPath()).thenReturn(TEST_FILE_1_PATH);

        return smbFile1Mock;
    }

	/**
	 * Mock for SMB file #2 (1-st level file).
	 *
	 * @throws IOException
	 */
    private SmbFile mockFile2() throws IOException {
        SmbFile smbFile2Mock = Mockito.mock(SmbFile.class);
        Mockito.when(smbFile2Mock.isDirectory()).thenReturn(Boolean.FALSE);
        Mockito.when(smbFile2Mock.isFile()).thenReturn(Boolean.TRUE);
        Mockito.when(smbFile2Mock.getInputStream()).thenReturn(null);
        Mockito.when(smbFile2Mock.getName()).thenReturn(TEST_FILE_2);
        Mockito.when(smbFile2Mock.getPath()).thenReturn(TEST_FILE_2_PATH);

        return smbFile2Mock;
    }

	/**
	 * Mock for SMB file #3 (2-nd level file).
	 *
	 * @throws IOException
	 */
    private SmbFile mockFile3() throws IOException {
        SmbFile smbFile3Mock = Mockito.mock(SmbFile.class);
        Mockito.when(smbFile3Mock.isDirectory()).thenReturn(Boolean.FALSE);
        Mockito.when(smbFile3Mock.isFile()).thenReturn(Boolean.TRUE);
        Mockito.when(smbFile3Mock.getInputStream()).thenReturn(null);
        Mockito.when(smbFile3Mock.getName()).thenReturn(TEST_FOLDER_1_FILE_1);
        Mockito.when(smbFile3Mock.getPath()).thenReturn(TEST_FOLDER_1_FILE_1_PATH);

        return smbFile3Mock;
    }

	/**
	 * Mock for SMB folder #1 (1-st level folder).
	 *
	 * @throws IOException
	 */
    private SmbFile mockFolder1(final SmbFile folderFile) throws IOException {
        SmbFile smbFolder1Mock = Mockito.mock(SmbFile.class);
        Mockito.when(smbFolder1Mock.isDirectory()).thenReturn(Boolean.TRUE);
        Mockito.when(smbFolder1Mock.isFile()).thenReturn(Boolean.FALSE);
        Mockito.when(smbFolder1Mock.getInputStream()).thenReturn(null);
        Mockito.when(smbFolder1Mock.getName()).thenReturn(TEST_FOLDER_1);
        Mockito.when(smbFolder1Mock.getPath()).thenReturn(TEST_FOLDER_1_PATH);
        Mockito.when(smbFolder1Mock.listFiles()).thenReturn(new SmbFile[] {folderFile});

        return smbFolder1Mock;
    }

    private SmbFile mockSmbHomeDir(SmbFile... homeDirFiles) throws IOException {
        SmbFile smbHomeDirMock = Mockito.mock(SmbFile.class);
        Mockito.when(smbHomeDirMock.listFiles(ArgumentMatchers.any(SmbFilenameFilter.class))).thenReturn(homeDirFiles);
        Mockito.when(smbHomeDirMock.listFiles()).thenReturn(homeDirFiles);
        return smbHomeDirMock;
    }
}
