package org.xbib.files.ftp.test.parser;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import org.xbib.files.ftp.FTPFile;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.EnumSet;
import org.xbib.files.ftp.parser.UnixFTPEntryParser;

public class UnixFTPEntryParserTest {

    private static final String[] badsamples = {
            "zrwxr-xr-x   2 root     root         4096 Mar  2 15:13 zxbox",
            "dxrwr-xr-x   2 root     root         4096 Aug 24  2001 zxjdbc",
            "drwxr-xr-x   2 root     root         4096 Jam  4 00:03 zziplib",
            "drwxr-xr-x   2 root     99           4096 Feb 23 30:01 zzplayer",
            "drwxr-xr-x   2 root     root         4096 Aug 36  2001 zztpp",
            "-rw-r--r--   1 14       staff       80284 Aug 22  zxJDBC-1.2.3.tar.gz",
            "-rw-r--r--   1 14       staff      119:26 Aug 22  2000 zxJDBC-1.2.3.zip",
            /*"-rw-r--r--   1 ftp      no group    83853 Jan 22  2001 zxJDBC-1.2.4.tar.gz",*/
            "-rw-r--r--   1ftp       nogroup    126552 Jan 22  2001 zxJDBC-1.2.4.zip",
            "-rw-r--r--   1 root     root       190144 2001-04-27 zxJDBC-2.0.1b1.zip",
            "-rw-r--r--   1 root     root       111325 Apr -7 18:79 zxJDBC-2.0.1b1.tar.gz"};

    private static final String[] goodsamples =
            {
                    "-rw-r--r--   1 500      500            21 Aug  8 14:14 JB3-TES1.gz",
                    "-rwxr-xr-x   2 root     root         4096 Mar  2 15:13 zxbox",
                    "drwxr-xr-x   2 root     root         4096 Aug 24  2001 zxjdbc",
                    "drwxr-xr-x   2 root     root         4096 Jan  4 00:03 zziplib",
                    "drwxr-xr-x   2 root     99           4096 Feb 23  2001 zzplayer",
                    "drwxr-xr-x   2 root     root         4096 Aug  6  2001 zztpp",
                    "drwxr-xr-x 1 usernameftp 512 Jan 29 23:32 prog",
                    "lrw-r--r--   1 14       14          80284 Aug 22  2000 zxJDBC-1.2.3.tar.gz",
                    "frw-r--r--   1 14       staff      119926 Aug 22  2000 zxJDBC-1.2.3.zip",
                    "crw-r--r--   1 ftp      nogroup     83853 Jan 22  2001 zxJDBC-1.2.4.tar.gz",
                    "brw-r--r--   1 ftp      nogroup    126552 Jan 22  2001 zxJDBC-1.2.4.zip",
                    "-rw-r--r--   1 root     root       111325 Apr 27  2001 zxJDBC-2.0.1b1.tar.gz",
                    "-rw-r--r--   1 root     root       190144 Apr 27  2001 zxJDBC-2.0.1b1.zip",
                    "-rwxr-xr-x   2 500      500           166 Nov  2  2001 73131-testtes1.afp",
                    "-rw-r--r--   1 500      500           166 Nov  9  2001 73131-testtes1.AFP",
                    "-rw-r--r--   1 500      500           166 Nov 12  2001 73131-testtes2.afp",
                    "-rw-r--r--   1 500      500           166 Nov 12  2001 73131-testtes2.AFP",
                    "-rw-r--r--   1 500      500       2040000 Aug  5 07:35 testRemoteUPCopyNIX",
                    "-rw-r--r--   1 500      500       2040000 Aug  5 07:31 testRemoteUPDCopyNIX",
                    "-rw-r--r--   1 500      500       2040000 Aug  5 07:31 testRemoteUPVCopyNIX",
                    "-rw-r--r-T   1 500      500             0 Mar 25 08:20 testSticky",
                    "-rwxr-xr-t   1 500      500             0 Mar 25 08:21 testStickyExec",
                    "-rwSr-Sr--   1 500      500             0 Mar 25 08:22 testSuid",
                    "-rwsr-sr--   1 500      500             0 Mar 25 08:23 testSuidExec",
                    "-rwsr-sr--   1 500      500             0 Mar 25 0:23 testSuidExec2",
                    "drwxrwx---+ 23 500     500    0 Jan 10 13:09 testACL",
                    "-rw-r--r--   1 1        3518644 May 25 12:12 std",
                    "lrwxrwxrwx   1 neeme neeme             23 Mar  2 18:06 macros -> ./../../global/macros/.",
                    "-rw-r--r--   1 ftp      group with spaces in it as allowed in cygwin see bug 38634" +
                            "   83853 Jan 22  2001 zxJDBC-1.2.4.tar.gz",
                    // Bug 38634 => NET-16
                    "crw-r----- 1 root kmem 0, 27 Jan 30 11:42 kmem",  //FreeBSD device
                    "crw-------   1 root     sys      109,767 Jul  2  2004 pci@1c,600000:devctl", //Solaris device
                    "-rwxrwx---   1 ftp      ftp-admin 816026400 Oct  5  2008 bloplab 7 cd1.img", // NET-294

                    // http://mail-archives.apache.org/mod_mbox/commons-dev/200408.mbox/%3c4122F3C1.9090402@tanukisoftware.com%3e
                    "-rw-r--r-- 1 1 3518644 May 25 12:12 std",
                    "-rw-rw---- 1 ep1adm sapsys 0 6\u6708 3\u65e5 2003\u5e74 \u8a66\u9a13\u30d5\u30a1\u30a4\u30eb.csv",
                    "-rw-rw---- 1 ep1adm sapsys 0 8\u6708 17\u65e5 20:10 caerrinf",

            };

    @Test
    public void testBadListing() {
        UnixFTPEntryParser parser = new UnixFTPEntryParser();
        for (String test : badsamples) {
            try {
                FTPFile f = parser.parseFTPEntry(test);
                assertNull(nullFileOrNullDate(f), "Should have Failed to parse <" + test + ">");
            } catch (DateTimeParseException e) {
                //
            }
        }
    }

    private FTPFile nullFileOrNullDate(FTPFile f) {
        if (f == null) {
            return null;
        }
        if (f.getTimestamp() == null) {
            return null;
        }
        return f;
    }

    @Test
    public void testGoodListing() {
        UnixFTPEntryParser parser = new UnixFTPEntryParser();
        for (String test : goodsamples) {
            FTPFile f = parser.parseFTPEntry(test);
            assertNotNull(f, "Failed to parse " + test);
            doAdditionalGoodTests(test, f);
        }
    }

    @Test
    public void testNumericDateFormat() {
        String testNumericDF = "-rw-r-----   1 neeme neeme   346 2005-04-08 11:22 services.vsp";
        String testNumericDF2 = "lrwxrwxrwx   1 neeme neeme    23 2005-03-02 18:06 macros -> ./../../global/macros/.";
        UnixFTPEntryParser parser = new UnixFTPEntryParser(UnixFTPEntryParser.NUMERIC_DATE_CONFIG);
        FTPFile f = parser.parseFTPEntry(testNumericDF);
        assertNotNull(f, "Failed to parse " + testNumericDF);
        ZonedDateTime zonedDateTime = ZonedDateTime.of(2005, Month.APRIL.getValue(), 8, 11, 22, 0, 0, ZoneId.of("UTC"));
        if (zonedDateTime.isAfter(ZonedDateTime.now())) {
            zonedDateTime = zonedDateTime.minusYears(1);
        }
        assertEquals(zonedDateTime, f.getTimestamp());
        FTPFile f2 = parser.parseFTPEntry(testNumericDF2);
        assertNotNull(f2, "Failed to parse " + testNumericDF2);
        assertEquals("./../../global/macros/.", f2.getLink(), "symbolic link");
    }

    @Test
    public void testOwnerNameWithSpaces() {
        UnixFTPEntryParser parser = new UnixFTPEntryParser();
        FTPFile f = parser.parseFTPEntry("drwxr-xr-x   2 john smith     group         4096 Mar  2 15:13 zxbox");
        assertNotNull(f);
        assertEquals("john smith", f.getUser());
    }

    @Test
    public void testOwnerAndGroupNameWithSpaces() {
        UnixFTPEntryParser parser = new UnixFTPEntryParser();
        FTPFile f = parser.parseFTPEntry("drwxr-xr-x   2 john smith     test group         4096 Mar  2 15:13 zxbox");
        assertNotNull(f);
        assertEquals("john smith", f.getUser());
        assertEquals("test group", f.getGroup());
    }

    @Test
    public void testNET294() {
        UnixFTPEntryParser parser = new UnixFTPEntryParser();
        FTPFile f = parser.parseFTPEntry("-rwxrwx---   1 ftp      ftp-admin 816026400 Oct  5  2008 bloplab 7 cd1.img");
        assertNotNull(f);
        assertEquals("ftp", f.getUser());
        assertEquals("ftp-admin", f.getGroup());
        assertEquals(816026400L, f.getSize());
        assertNotNull(f.getTimestamp(), "Timestamp should not be null");
        assertEquals(2008, f.getTimestamp().getYear());
        assertEquals("bloplab 7 cd1.img", f.getName());
    }

    @Test
    public void testGroupNameWithSpaces() {
        UnixFTPEntryParser parser = new UnixFTPEntryParser();
        FTPFile f = parser.parseFTPEntry("drwx------ 4 maxm Domain Users 512 Oct 2 10:59 .metadata");
        assertNotNull(f);
        assertEquals("maxm", f.getUser());
        assertEquals("Domain Users", f.getGroup());
    }

    @Test
    public void testTrailingSpaces() {
        UnixFTPEntryParser parser = new UnixFTPEntryParser();
        FTPFile f = parser.parseFTPEntry("drwxr-xr-x   2 john smith     group         4096 Mar  2 15:13 zxbox     ");
        assertNotNull(f);
        assertEquals("zxbox     ", f.getName());
    }

    @Test
    public void testLeadingSpacesDefault() { // the default has been changed to keep spaces
        UnixFTPEntryParser parser = new UnixFTPEntryParser();
        FTPFile f = parser.parseFTPEntry("drwxr-xr-x   2 john smith     group         4096 Mar  2 15:13   zxbox");
        assertNotNull(f);
        assertEquals("  zxbox", f.getName()); // leading spaces retained
    }

    @Test
    public void testLeadingSpacesNET566() { // check new behaviour
        FTPFile f = new UnixFTPEntryParser(null, false)
                .parseFTPEntry("drwxr-xr-x   2 john smith     group         4096 Mar  2 15:13   zxbox");
        assertNotNull(f);
        assertEquals("  zxbox", f.getName()); // leading spaces retained
    }

    @Test
    public void testTrimLeadingSpacesNET566() { // check can trim spaces as before
        FTPFile f = new UnixFTPEntryParser(null, true)
                .parseFTPEntry("drwxr-xr-x   2 john smith     group         4096 Mar  2 15:13   zxbox");
        assertNotNull(f);
        assertEquals("zxbox", f.getName()); // leading spaces trimmed
    }

    @Test
    public void testNameWIthPunctuation() {
        UnixFTPEntryParser parser = new UnixFTPEntryParser();
        FTPFile f = parser.parseFTPEntry("drwx------ 4 maxm Domain Users 512 Oct 2 10:59 abc(test)123.pdf");
        assertNotNull(f);
        assertEquals("abc(test)123.pdf", f.getName());
    }

    @Test
    public void testNoSpacesBeforeFileSize() {
        UnixFTPEntryParser parser = new UnixFTPEntryParser();
        FTPFile f = parser.parseFTPEntry("drwxr-x---+1464 chrism   chrism     41472 Feb 25 13:17 20090225");
        assertNotNull(f);
        assertEquals(41472, f.getSize());
        assertEquals(f.getType(), FTPFile.DIRECTORY_TYPE);
        assertEquals("chrism", f.getUser());
        assertEquals("chrism", f.getGroup());
        assertEquals(1464, f.getHardLinkCount());
    }

    @Test
    public void testCorrectGroupNameParsing() {
        UnixFTPEntryParser parser = new UnixFTPEntryParser();
        FTPFile f = parser.parseFTPEntry("-rw-r--r--   1 ftpuser  ftpusers 12414535 Mar 17 11:07 test 1999 abc.pdf");
        assertNotNull(f);
        assertEquals(1, f.getHardLinkCount());
        assertEquals("ftpuser", f.getUser());
        assertEquals("ftpusers", f.getGroup());
        assertEquals(12414535, f.getSize());
        assertEquals("test 1999 abc.pdf", f.getName());
        ZonedDateTime zonedDateTime = ZonedDateTime.of(ZonedDateTime.now().getYear(),
                Month.MARCH.getValue(), 17, 11, 7, 0, 0, ZoneId.of("UTC"));
        if (zonedDateTime.isAfter(ZonedDateTime.now())) {
            zonedDateTime = zonedDateTime.minusYears(1);
        }
        assertEquals(zonedDateTime.getMonth(), f.getTimestamp().getMonth());
        assertEquals(zonedDateTime.getDayOfMonth(), f.getTimestamp().getDayOfMonth());
        assertEquals(zonedDateTime.getHour(), f.getTimestamp().getHour());
        assertEquals(zonedDateTime.getMinute(), f.getTimestamp().getMinute());
        assertEquals(zonedDateTime.getSecond(), f.getTimestamp().getSecond());
    }

    @Test
    public void testFilenamesWithEmbeddedNumbers() {
        UnixFTPEntryParser parser = new UnixFTPEntryParser();
        FTPFile f = parser.parseFTPEntry("-rw-rw-rw-   1 user group 5840 Mar 19 09:34 123 456 abc.csv");
        assertEquals("123 456 abc.csv", f.getName());
        assertEquals(5840, f.getSize());
        assertEquals("user", f.getUser());
        assertEquals("group", f.getGroup());
    }

    @Test
    public void testParseFieldsOnDirectory() throws Exception {
        UnixFTPEntryParser parser = new UnixFTPEntryParser();
        FTPFile f = parser.parseFTPEntry("drwxr-xr-x   2 user     group         4096 Mar  2 15:13 zxbox");
        assertNotNull(f, "Could not parse entry.");
        assertTrue(f.isDirectory(), "Should have been a directory.");
        checkPermissions(f);
        assertEquals(2, f.getHardLinkCount());
        assertEquals("user", f.getUser());
        assertEquals("group", f.getGroup());
        assertEquals("zxbox", f.getName());
        assertEquals(4096, f.getSize());
        ZonedDateTime zonedDateTime = ZonedDateTime.of(ZonedDateTime.now().getYear(),
                Month.MARCH.getValue(), 2, 15, 13, 0, 0, ZoneId.of("UTC"));
        if (zonedDateTime.isAfter(ZonedDateTime.now())) {
            zonedDateTime = zonedDateTime.minusYears(1);
        }
        assertEquals(zonedDateTime, f.getTimestamp());
    }

    @Test
    public void testRecentPrecision() {
        testPrecision("drwxr-xr-x   2 user     group         4096 Mar  2 15:13 zxbox",
                EnumSet.of(ChronoUnit.MINUTES));
    }

    @Test
    public void testDefaultPrecision() {
        testPrecision("drwxr-xr-x   2 user     group         4096 Mar  2 2014 zxbox",
                EnumSet.of(ChronoUnit.DAYS));
    }

    private void testPrecision(String listEntry, EnumSet<ChronoUnit> units) {
        UnixFTPEntryParser parser = new UnixFTPEntryParser();
        FTPFile file = parser.parseFTPEntry(listEntry);
        assertNotNull(file, "Could not parse " + listEntry);
        ZonedDateTime zonedDateTime = file.getTimestamp();
        assertNotNull(zonedDateTime, "Failed to parse time in " + listEntry);
        for (ChronoUnit unit : units) {
            assertTrue(zonedDateTime.isSupported(unit), "Expected set " + unit + " in " + listEntry);
        }
    }

    private void checkPermissions(FTPFile f) {
        assertTrue(f.hasPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION), "Should have user read permission.");
        assertTrue(f.hasPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION), "Should have user write permission.");
        assertTrue(f.hasPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION), "Should have user execute permission.");
        assertTrue(f.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.READ_PERMISSION), "Should have group read permission.");
        assertFalse(f.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.WRITE_PERMISSION), "Should NOT have group write permission.");
        assertTrue(f.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.EXECUTE_PERMISSION), "Should have group execute permission.");
        assertTrue(f.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.READ_PERMISSION), "Should have world read permission.");
        assertFalse(f.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.WRITE_PERMISSION), "Should NOT have world write permission.");
        assertTrue(f.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.EXECUTE_PERMISSION), "Should have world execute permission.");
    }

    @Test
    public void testParseFieldsOnFile() {
        UnixFTPEntryParser parser = new UnixFTPEntryParser();
        FTPFile f = parser.parseFTPEntry("-rwxr-xr-x   2 user     my group 500        5000000000 Mar  2 15:13 zxbox");
        assertNotNull(f, "Could not parse entry.");
        assertTrue(f.isFile(), "Should have been a file.");
        checkPermissions(f);
        assertEquals(2, f.getHardLinkCount());
        assertEquals("user", f.getUser());
        assertEquals("my group 500", f.getGroup());
        assertEquals("zxbox", f.getName());
        assertEquals(5000000000L, f.getSize());
        ZonedDateTime zonedDateTime = ZonedDateTime.of(ZonedDateTime.now().getYear(),
                Month.MARCH.getValue(), 2, 15, 13, 0, 0, ZoneId.of("UTC"));
        if (zonedDateTime.isAfter(ZonedDateTime.now())) {
            zonedDateTime = zonedDateTime.minusYears(1);
        }
        assertEquals(zonedDateTime, f.getTimestamp());
    }

    @Test
    public void testParseFieldsOnFileJapaneseTime() {
        UnixFTPEntryParser parser = new UnixFTPEntryParser();
        FTPFile f = parser.parseFTPEntry("-rwxr-xr-x 2 user group 4096 3\u6708 2\u65e5 15:13 zxbox");
        assertNotNull(f, "Could not parse entry.");
        assertTrue(f.isFile(), "Should have been a file.");
        checkPermissions(f);
        assertEquals(2, f.getHardLinkCount());
        assertEquals("user", f.getUser());
        assertEquals("group", f.getGroup());
        assertEquals("zxbox", f.getName());
        assertEquals(4096, f.getSize());
        assertNotNull(f.getTimestamp(), "Timestamp not null");
        ZonedDateTime zonedDateTime = ZonedDateTime.of(ZonedDateTime.now().getYear(),
                Month.MARCH.getValue(), 2, 15, 13, 0, 0, ZoneId.of("UTC"));
        if (zonedDateTime.isAfter(ZonedDateTime.now())) {
            zonedDateTime = zonedDateTime.minusYears(1);
        }
        assertEquals(zonedDateTime, f.getTimestamp());
    }

    @Test
    public void testParseFieldsOnFileJapaneseYear() {
        UnixFTPEntryParser parser = new UnixFTPEntryParser();
        FTPFile f = parser.parseFTPEntry("-rwxr-xr-x 2 user group 4096 3\u6708 2\u65e5 2003\u5e74 \u8a66\u9a13\u30d5\u30a1\u30a4\u30eb.csv");
        assertNotNull(f, "Could not parse entry.");
        assertTrue(f.isFile(), "Should have been a file.");
        checkPermissions(f);
        assertEquals(2, f.getHardLinkCount());
        assertEquals("user", f.getUser());
        assertEquals("group", f.getGroup());
        assertEquals("\u8a66\u9a13\u30d5\u30a1\u30a4\u30eb.csv", f.getName());
        assertEquals(4096, f.getSize());
        assertNotNull(f.getTimestamp(), "Timestamp not null");
        ZonedDateTime zonedDateTime = ZonedDateTime.of(2003,
                Month.MARCH.getValue(), 2, 0, 0, 0, 0, ZoneId.of("UTC"));
        assertEquals(zonedDateTime, f.getTimestamp());
    }

    private void doAdditionalGoodTests(String test, FTPFile f) {
        String link = f.getLink();
        if (null != link) {
            int linklen = link.length();
            if (linklen > 0) {
                assertEquals(link, test.substring(test.length() - linklen));
                assertEquals(f.getType(), FTPFile.SYMBOLIC_LINK_TYPE);
            }
        }
        int type = f.getType();
        switch (test.charAt(0)) {
            case 'd':
                assertEquals(type, FTPFile.DIRECTORY_TYPE, "Type of " + test);
                break;
            case 'l':
                assertEquals(type, FTPFile.SYMBOLIC_LINK_TYPE, "Type of " + test);
                break;
            case 'b':
            case 'c':
                assertEquals(0, f.getHardLinkCount());
                assertEquals(type, FTPFile.FILE_TYPE, "Type of " + test);
                break;
            case 'f':
                assertEquals(type, FTPFile.FILE_TYPE, "Type of " + test);
                break;
            case '-':
                assertEquals(type, FTPFile.FILE_TYPE, "Type of " + test);
                break;
            default:
                assertEquals(type, FTPFile.UNKNOWN_TYPE, "Type of " + test);
        }
        for (int access = FTPFile.USER_ACCESS;
             access <= FTPFile.WORLD_ACCESS; access++) {
            for (int perm = FTPFile.READ_PERMISSION;
                 perm <= FTPFile.EXECUTE_PERMISSION; perm++) {
                int pos = 3 * access + perm + 1;
                char permchar = test.charAt(pos);
                assertEquals(f.hasPermission(access, perm),
                        permchar != '-' && !Character.isUpperCase(permchar),
                        "Permission " + test.substring(1, 10));
            }
        }
        assertNotNull(f.getTimestamp(), "Expected to find a timestamp");
    }
}
