package it.com.atlassian.stash;

import com.atlassian.pageobjects.elements.query.Conditions;
import com.atlassian.pageobjects.elements.query.Poller;
import com.atlassian.webdriver.bitbucket.element.RevisionHistoryDialog;
import com.atlassian.webdriver.bitbucket.element.SearchableSelector;
import com.atlassian.webdriver.bitbucket.element.SourceFileContent;
import com.atlassian.webdriver.bitbucket.page.FileLayoutPage;
import com.atlassian.webdriver.bitbucket.page.FileSourcePage;
import com.atlassian.webdriver.bitbucket.util.DateUtils;
import com.atlassian.webdriver.pageobjects.DefaultWebDriverTester;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;

import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

public class ViewSourceTest extends BaseFileContentTest {

    @Test
    public void testUnicodePath() throws Exception {
        String unicodeDir = "z\u56fd\u969b\u4ea4\u6d41\u57fa\u91d1\u6d77\u5916\u904b\u55b6\u5c02\u9580\u54e1";
        String fileName = "test.txt";
        FileSourcePage page = loginAsAdmin(FileSourcePage.class, getProjectKey(), getRepoSlug(), getUrlPathComponent(unicodeDir, fileName));

        assertEquals("master", page.getBranchSelector().getSelectedItemName());

        assertBreadcrumbs(page.getBreadcrumbs(), getRepoSlug(), unicodeDir, fileName);

        assertEquals("Federico Silva Armas", page.getCurrentRevisionAuthor());
        assertEquals("03fe9269cb7", page.getCurrentRevisionId());

        assertCorrectDate(page, "2012-02-05T19:04:41-0600");

        List<String> source = page.getFileContent().getSourceLines();
        assertThat(source, hasSize(1));
        assertThat(source, contains("hello"));
    }

    @Test
    public void testRawFile() throws Exception {
        String unicodeDir = "z\u56fd\u969b\u4ea4\u6d41\u57fa\u91d1\u6d77\u5916\u904b\u55b6\u5c02\u9580\u54e1";
        String fileName = "test.txt";
        FileSourcePage page = loginAsAdmin(FileSourcePage.class, getProjectKey(), getRepoSlug(), getUrlPathComponent(unicodeDir, fileName));

        String fileUrl = page.getRawLink();
        DefaultWebDriverTester tester = new DefaultWebDriverTester();
        tester.gotoUrl(fileUrl);
        String source = tester.getDriver().getPageSource();
        assertEquals("hello", source);

        String baseUri = StringUtils.substringBefore(fileUrl, "raw/");
        String rawLink = baseUri + getUrlPathComponent("browse", unicodeDir, fileName) + "?raw"; //Get the old endpoint
        tester.gotoUrl(rawLink);
        assertEquals(source, tester.getDriver().getPageSource());
    }

    @Test
    public void testLoadUntilRevision() throws Exception {
        checkLoadUntilInTz(Optional.empty());
    }

    @Test
    public void testLoadUntilRevisionInServerTz() throws Exception {
        TimeZoneTestHelper.withServerTimeZone(Optional.of(ZoneOffset.MAX), this::checkLoadUntilInTz);
        TimeZoneTestHelper.withServerTimeZone(Optional.of(ZoneOffset.MIN), this::checkLoadUntilInTz);
    }

    @Test
    public void testLoadUntilRevisionInUserTz() throws Exception {
        TimeZoneTestHelper.withUserTimeZone(Optional.of(ZoneOffset.MAX), this::checkLoadUntilInTz);
        TimeZoneTestHelper.withUserTimeZone(Optional.of(ZoneOffset.MIN), this::checkLoadUntilInTz);
    }

    @Test
    public void testBlame() throws Exception {
        checkBlameInTz(Optional.empty());
    }

    @Test
    public void testBlameInServerTz() {
        TimeZoneTestHelper.withServerTimeZone(Optional.of(ZoneOffset.MIN), this::checkBlameInTz);
        TimeZoneTestHelper.withServerTimeZone(Optional.of(ZoneOffset.MAX), this::checkBlameInTz);
    }

    @Test
    public void testBlameInUserTz() {
        TimeZoneTestHelper.withUserTimeZone(Optional.of(ZoneOffset.MIN), this::checkBlameInTz);
        TimeZoneTestHelper.withUserTimeZone(Optional.of(ZoneOffset.MAX), this::checkBlameInTz);
    }

    @Test
    public void testSwitchUntilRevision() throws Exception {
        String fileName = "file1.txt";
        String revision = "703454986a113eb3087459fd155acc1812f5c439";
        FileLayoutPage.RevisionSpecifier revisionSpecifier = new FileLayoutPage.RevisionSpecifier(null, revision);
        FileSourcePage page = loginAsAdmin(FileSourcePage.class, getProjectKey(), getRepoSlug(), getUrlPathComponent(fileName), revisionSpecifier);

        assertEquals("master", page.getBranchSelector().getSelectedItemName());

        assertBreadcrumbs(page.getBreadcrumbs(), getRepoSlug(), fileName);

        assertEquals("\u56fd\u969b\u4ea4\u6d41\u57fa\u91d1\u6d77\u5916\u904b\u55b6\u5c02\u9580\u54e1", page.getCurrentRevisionAuthor());
        assertCorrectDate(page, "2012-01-29T18:52:48-0500");

        RevisionHistoryDialog revisionHistoryDialog = page.getRevisionHistoryDialog();

        revisionHistoryDialog.open();
        revisionHistoryDialog.loadNextPage();
        page = page.loadUntilRevision("a0e5b52e38ba73d1d08a7a343341ab5d2a588ddc");

        assertEquals("master", page.getBranchSelector().getSelectedItemName());

        assertBreadcrumbs(page.getBreadcrumbs(), getRepoSlug(), fileName);

        assertEquals("Federico Silva Armas", page.getCurrentRevisionAuthor());
        assertCorrectDate(page, "2012-02-05T17:38:56-0500");

        revisionHistoryDialog = page.getRevisionHistoryDialog();

        // Load two pages
        revisionHistoryDialog.open();
        revisionHistoryDialog.loadNextPage();
        revisionHistoryDialog.loadNextPage();
        page = page.loadUntilRevision("b05d551e3800564e8c4747d65aab28a8c957e185");

        assertEquals("master", page.getBranchSelector().getSelectedItemName());

        assertBreadcrumbs(page.getBreadcrumbs(), getRepoSlug(), fileName);

        assertEquals("\u56fd\u969b\u4ea4\u6d41\u57fa\u91d1\u6d77\u5916\u904b\u55b6\u5c02\u9580\u54e1", page.getCurrentRevisionAuthor());
        assertCorrectDate(page, "2012-01-30T10:52:56+1100");
    }

    @Test
    public void testSwitchBranchFileDoesNotExist() {
        String fileName = "newfile.txt";
        String revision = "new-branch";
        FileLayoutPage.RevisionSpecifier revisionSpecifier = new FileLayoutPage.RevisionSpecifier(revision, null);

        FileSourcePage page = loginAsAdmin(FileSourcePage.class, getProjectKey(), getRepoSlug(), getUrlPathComponent(fileName), revisionSpecifier);

        SearchableSelector branchSelector = page.getBranchSelector();
        assertEquals(revision, branchSelector.getSelectedItemName());

        branchSelector.open().filterBy("master").selectItemById("refs/heads/master");
        assertEquals("master", branchSelector.getSelectedItemName());

        Poller.waitUntilTrue("Expected warning message", Conditions.forSupplier(5000, () -> !page.getWarningMessages().isEmpty()));
        Collection<String> auiMessages = page.getWarningMessages();
        assertEquals("The path \"newfile.txt\" does not exist at revision \"master\"", auiMessages.toArray()[0]);
    }

    @Test
    public void testLoadSourcePagination() throws Exception {
        String dir = "SourceView";
        String file = "25000lines-csharp.cs";
        String revision = "2a05b118bf21a8a415b7130a0982ee4e5713fc26";
        FileLayoutPage.RevisionSpecifier revisionSpecifier = new FileLayoutPage.RevisionSpecifier(null, revision);
        FileSourcePage page = loginAsAdmin(FileSourcePage.class, getProjectKey(), getRepoSlug(), getUrlPathComponent(dir, file), revisionSpecifier);

        assertEquals("master", page.getBranchSelector().getSelectedItemName());

        assertBreadcrumbs(page.getBreadcrumbs(), getRepoSlug(), dir, file);

        assertEquals("\u56fd\u969b\u4ea4\u6d41\u57fa\u91d1\u6d77\u5916\u904b\u55b6\u5c02\u9580\u54e1", page.getCurrentRevisionAuthor());
        assertEquals("2a05b118bf2", page.getCurrentRevisionId());

        assertCorrectDate(page, "2012-01-29T18:46:53-0600");

        SourceFileContent fileContent = page.getFileContent();
        List<String> source = fileContent.getSourceLines();
        assertEquals(20000, source.size());
        assertThat(source.get(0), containsString("using System;")); //Byte order mark as first character breaks equals
        assertThat(source.get(4999), containsString("var issues = (from i in jira.Issues")); //Indenting breaks equals

        fileContent.loadNextPage();

        source = fileContent.getSourceLines();
        assertEquals(20000, source.size());
        assertThat(source.get(0), containsString("using System;")); //Byte order mark as first character breaks equals
        assertThat(source.get(4999), containsString("var issues = (from i in jira.Issues")); //Indenting breaks equals
        assertThat(source.get(5000), containsString("where i.Votes < 5"));                   //Indenting breaks equals
        assertThat(source.get(9999).trim(), containsString("select i).ToArray();"));         //Indenting breaks equals

        Collection<String> auiMessages = page.getWarningMessages();
        String firstMessage = auiMessages.iterator().next();
        assertTrue(firstMessage.contains("too large"));
    }

    //Starting in 4.5, CR by itself is no longer _ever_ considered a line ending by the server. That means, where
    //CRCRLF.txt used to end up using MODE_MAC line endings, resulting in line-break (CR), line-break (CR), stray
    //LF, now it results in stray CR, line-break (CRLF). The UI strips out the stray CR or LF, to avoid rendering
    //issues. So, prior to 4.5, this test produced 51 lines (since there were two CRs in a row). Starting in 4.5,
    //it now produces only 25 (and looks significantly better for it).
    @Test
    public void testStrangeLineEndingsHandledCorrectly() throws Exception {
        String fileName = "CRCRLF.txt";
        FileSourcePage page = loginAsAdmin(FileSourcePage.class, getProjectKey(), getRepoSlug(), getUrlPathComponent(fileName));

        assertEquals("master", page.getBranchSelector().getSelectedItemName());

        assertBreadcrumbs(page.getBreadcrumbs(), getRepoSlug(), fileName);

        List<String> source = page.getFileContent().getSourceLines();
        assertEquals(25, source.size()); //There will be more lines if the extraneous newlines are not handled properly
    }

    private static void assertBlameRow(FileSourcePage.BlameRow row, String author, String commitId, String date,
                                       int span, Optional<? extends ZoneId> empty) {
        assertEquals(author, row.getAuthor());
        assertEquals(commitId, row.getCommit());
        assertThat(row.getDate(), DateUtils.SHORT.withTimeZone(empty).matchesTimestamp(date));
        assertEquals(span, row.getSpan());
    }

    private Void checkBlameInTz(Optional<? extends ZoneId> timeZone) {
        String fileName = "file1.txt";

        String revision = "c7c7497cb7ec1a5d645f891c066a8d876f42459b";
        FileLayoutPage.RevisionSpecifier revisionSpecifier = new FileLayoutPage.RevisionSpecifier(null, revision);
        FileSourcePage page = loginAsAdmin(FileSourcePage.class, getProjectKey(), getRepoSlug(), getUrlPathComponent(fileName), revisionSpecifier);

        Iterator<FileSourcePage.BlameRow> rows = page.showBlame().iterator();
        assertBlameRow(rows.next(), "Federico Silva Armas", "b84b45e0b1d", "2012-01-17T10:10:27+1100", 1, timeZone);
        assertBlameRow(rows.next(), "Federico Silva Armas", "c7c7497cb7e", "2012-02-22T17:05:11+1100", 106, timeZone);
        assertBlameRow(rows.next(), "Federico Silva Armas", "b84b45e0b1d", "2012-01-17T10:10:27+1100", 2, timeZone);

        return null;
    }

    private Void checkLoadUntilInTz(Optional<? extends ZoneId> zoneId) {
        String fileName = "file1.txt";
        String revision = "c7c7497cb7ec1a5d645f891c066a8d876f42459b";
        FileLayoutPage.RevisionSpecifier revisionSpecifier = new FileLayoutPage.RevisionSpecifier(null, revision);
        FileSourcePage page = loginAsAdmin(FileSourcePage.class, getProjectKey(), getRepoSlug(), getUrlPathComponent(fileName), revisionSpecifier);

        assertEquals("master", page.getBranchSelector().getSelectedItemName());

        assertBreadcrumbs(page.getBreadcrumbs(), getRepoSlug(), fileName);

        assertEquals("Federico Silva Armas", page.getCurrentRevisionAuthor());
        assertEquals("c7c7497cb7e", page.getCurrentRevisionId());

        assertCorrectDate(page, "2012-02-22T01:05:11-0500", zoneId);

        return null;
    }
}
